Migrando aplicações do mundo real para o java se 8

65
Migrando aplicações do mundo real para o Java SE 8 Janario Oliveira | @janarioliver Michael Nascimento Santos | @mr_ _m Michel Graciano | @mgraciano

description

Slides tdc2013 - Trilha Java

Transcript of Migrando aplicações do mundo real para o java se 8

Page 1: Migrando aplicações do mundo real para o java se 8

Migrando aplicações do mundo real para o Java SE 8Janario Oliveira | @janarioliverMichael Nascimento Santos | @mr_ _mMichel Graciano | @mgraciano

Page 2: Migrando aplicações do mundo real para o java se 8

Apresentação● Michael Nascimento Santos

○ 14 anos de experiência com a plataforma Java e programador há 20 anos

○ Committer do OpenJDK○ Membro da organização do SouJava ○ JavaOne Rock Star Speaker○ Co-líder da JSR-310 (Date & Time API - java.time) e expert em mais 6

JSRs, inclusive a que definiu o Java SE 6○ Líder, arquiteto e desenvolvedor na TecSinapse

Page 3: Migrando aplicações do mundo real para o java se 8

● Janario Oliveira○ Mais de 4 anos de experiência com a plataforma Java○ Contribuições ativas em projetos opensource como Hibernate, JBoss

AS, NetBeans entre outros○ Desenvolvedor na TecSinapse

Apresentação

Page 4: Migrando aplicações do mundo real para o java se 8

● Michel Graciano○ Atualmente Arquiteto de Sistemas na Betha Sistemas e com mais de

10 anos de experiência com a plataforma Java○ Membro ativo de projetos open source como o NetBeans e genesis○ Já fez apresentações no JavaOne USA e Brasil, bem como em

algumas edições do TDC Floripa e JustJava.

Apresentação

Page 5: Migrando aplicações do mundo real para o java se 8

Agenda● Introdução rápida● Migrando aplicações para Java SE 8

○ O que podemos migrar automaticamente○ Tentando aprofundar o uso dos novos recursos

● Dificuldades e perdas de performance● Conclusão● Q&A

Page 6: Migrando aplicações do mundo real para o java se 8

DisclaimerNosso código e testes foram realizados com o b97(30/06/2013) do Lambda

Page 7: Migrando aplicações do mundo real para o java se 8

#java8mundoreal

Page 8: Migrando aplicações do mundo real para o java se 8

Introdução rápidaIntrodução aos principais conceitos e tecnologias do Java SE 8

Page 9: Migrando aplicações do mundo real para o java se 8

JSR 337: Java SE 8● Datas

○ 2013/09/05 Developer Preview○ 2014/01/23 Release Candidate○ 2014/03/18 Final Release

● Principais JSRs○ 294: Improved Modularity Support in the JavaTM Programming

Language (Jigsaw)○ 308: Annotations on Java Types (não tem API prática ainda)○ 310: Date and Time API○ 335: Lambda Expressions for the JavaTM Programming Language

Page 10: Migrando aplicações do mundo real para o java se 8

JSR 310: Date and Time● Spec Leads:

○ Stephen Colebourne - criador do Joda-Time○ Michael Nascimento Santos○ Roger Riggs

● Baseado e muito semelhante ao Joda-Time, porém melhor

Page 11: Migrando aplicações do mundo real para o java se 8

JSR 310: Date and Time● Imutável e thread-safe

● Utilize sempre as classes mais específicas para o problema

● YearMonth - Mês e anoYearMonth.of(2013, Month.JULY);

● LocalDate - Data sem hora ou time-zoneLocalDate.now();LocalDate dataTDC = LocalDate.of(2013, Month.JULY, 12);

● LocalTime - Hora sem data ou time-zoneLocalTime meiaNoite = LocalTime.MIDNIGHT;LocalTime onzeHoras = LocalTime.of(11, 0);assert meiaNoite.isBefore(onzeHoras);

Page 12: Migrando aplicações do mundo real para o java se 8

JSR 310: Date and Time● LocalDateTime - Data com hora sem time-zone

LocalDateTime dataTDCMeioDia = LocalDateTime.of(dataTDC, LocalTime.NOON);

LocalDateTime dataTDCOnzeHoras = dataTDC.atTime(11, 0);assert dataTDCMeioDia.minusHours(1).equals(dataTDCOnzeHoras);

● OffsetDateTime - Data com hora offset e sem time-zoneOffsetDateTime.of(dataTDCMeioDia, ZoneOffset. ofHours(-3));

● ZonedDateTime - Data com hora e time-zoneZonedDateTime.of(dataTDCMeioDia, ZoneId.of( "America/Sao_Paulo" ));

Page 13: Migrando aplicações do mundo real para o java se 8

JSR 310: Date and Time● Outras classes de domínio

○ Year○ Month - enum○ DayOfWeek - enum○ OffsetDate○ OffsetTime○ Period○ Instant○ Duration○ Clock

● Nova API de formatação● Diversos outros conceitos:

○ Temporals○ Adjusters○ Queries○ Units

Page 14: Migrando aplicações do mundo real para o java se 8

JSR 335: Lambda Expressions● Permite programação funcional, com maior nível de reutilização de código

e escrita concisaint maiorIdadeDePessoaDoSexoMasculino = -1;

for (Pessoa pessoa : pessoas) { if (pessoa.getSexo() == Sexo.MASCULINO) { int idade = pessoa.getIdade();

if (idade > maiorIdadeDePessoaDoSexoMasculino ) { maiorIdadeDePessoaDoSexoMasculino = idade; } }}

if (maiorIdadeDePessoaDoSexoMasculino != -1) { trataIdade(maiorIdadeDePessoaDoSexoMasculino );}

Page 15: Migrando aplicações do mundo real para o java se 8

JSR 335: Lambda Expressions● Permite programação funcional, com maior nível de reutilização de código

e escrita concisapessoas.stream() .filter(pessoa -> pessoa.getSexo() == Sexo.MASCULINO) .mapToInt(Pessoa::getIdade) .max() .ifPresent(PessoaProcessor::trataIdade);

Page 16: Migrando aplicações do mundo real para o java se 8

JSR 335: Lambda Expressions● Permite programação funcional, com maior nível de reutilização de código

e escrita concisapessoas.parallelStream() .filter(pessoa -> pessoa.getSexo() == Sexo.MASCULINO) .mapToInt(Pessoa::getIdade) .max() .ifPresent(PessoaProcessor::trataIdade);

Page 17: Migrando aplicações do mundo real para o java se 8

Migrando aplicações para Java SE 8

Page 18: Migrando aplicações do mundo real para o java se 8

Migrando aplicações para Java SE 8● Foram migradas duas aplicações:

○ Um BI customizado para indústria automobilística com diversos gráficos e relatórios

○ Uma aplicação 24x7 que será lançada em breve● Ambas com grande utilização do Guava, o que facilitou muito a migração

para utilização de Lambda Expressions○ Guava(code.google.com/p/guava-libraries) - Framework utilitário com

suporte a programação funcional● Forte utilização do Joda-Time, em especial o YearMonth por serem

gráficos que acumulam dados estatísticos mensais● Iniciamos há 8 meses e muita coisa vem sendo melhorada neste período

Page 19: Migrando aplicações do mundo real para o java se 8

O que podemos migrar automaticamente● NetBeans 8 Nightly Builds está em desenvolvimento e já oferece algumas

Hints para o Java SE 8 (Refactor > Inspect and Transform):○ Hint: Convert to Lambda

Page 20: Migrando aplicações do mundo real para o java se 8

O que podemos migrar automaticamente● NetBeans 8 Nightly Builds está em desenvolvimento e já oferece algumas

Hints para o Java SE 8 (Refactor > Inspect and Transform):○ Hint: Use Functions Operations

Page 21: Migrando aplicações do mundo real para o java se 8

O que podemos migrar automaticamente● Benéfica principalmente para projetos Java SE

○ Usam mais Functional Interfaces (interfaces de um método abstrato apenas), boas candidatas à migração

○ Runnable, listeners do Swing etc. são exemplos● Projetos Java EE só se beneficiarão mais se usarem alguma biblioteca

funcional○ Nós usamos :-)

Page 22: Migrando aplicações do mundo real para o java se 8

Tentando aprofundar o uso dos novos recursos● Guava nos ajudou na migração automática, mas agora precisávamos

eliminar para testar a API● As operações funcionais mais comuns do Guava tem equivalente quase

direto no Java SE 8:○ filter -> filter○ transform -> map○ limit -> limit

● E o resto?

Page 23: Migrando aplicações do mundo real para o java se 8

Tentando aprofundar o uso dos novos recursos● Como converter o resultado de uma operação funcional para uma List?List<String> names = brands.stream()

.map(Brand::getName) .collect(toList());

● Através de collectors (implementações padrão em Collectors) é que fazemos a maior parte das "terminal operations", i.e., converter de um stream para outra collection ou classe "sintetizadora" do resultado

Page 24: Migrando aplicações do mundo real para o java se 8

Tentando aprofundar o uso dos novos recursos● Como gerar um Map<Long,Brand>?//Padrão throwingMerger: java.lang.IllegalStateException: Duplicate keyMap<Long,Brand> brandById = brands.stream()

.collect(toMap(Brand::getId, identity()));

● Mas e se houver colisões? ○ Um parâmetro adicional, quando especificado, define a estratégia de

"merge":BinaryOperator<T>

T apply(T u, T v) (u,v) -> u; //firstWinsMerger () método removido no b97 (u,v) -> v; //lastWinsMerger () método removido no b97

Page 25: Migrando aplicações do mundo real para o java se 8

Tentando aprofundar o uso dos novos recursos● Mas e quando preciso de uma lista com colisões?Map<Holding,List<Brand>> brandByHolding = brands.stream()

.collect(groupingBy(Brand::getHolding));

Page 26: Migrando aplicações do mundo real para o java se 8

Tentando aprofundar o uso dos novos recursos● Como agregar elementos de uma coleção retornada pelo objeto do

stream?List<Dealer> branches = dealers.stream() .flatMap(dealer -> dealer.getBranches().stream())

.collect(toList());

Page 27: Migrando aplicações do mundo real para o java se 8

Tentando aprofundar o uso dos novos recursos● Novos métodos úteis em Map://Java 7Long l = totalByYearMonth.get(yearMonth);long total = l == null ? 0L : l;

//Java 8long total = totalByYearMonth. getOrDefault(yearMonth, 0L);

Page 28: Migrando aplicações do mundo real para o java se 8

Tentando aprofundar o uso dos novos recursos● Novos métodos úteis em Map://Java 7Map<Brand,Long> totalByBrand = totalByBrandByYearMonth.get(yearMonth);

if (totalByBrand == null) { totalByBrandByYearMonth.put(yearMonth, totalByBrand = new HashMap<>());}

Long t = totalByBrand.get(brand);totalByBrand.put(brand, t == null ? total : t + total);

//Java 8totalByBrandByYearMonth

.putIfAbsent(yearMonth, new HashMap<>())

.merge(brand, total, Long::sum);

Page 29: Migrando aplicações do mundo real para o java se 8

Date and Time● Formatador - por ser thread-safe é possível defini-lo em uma variável

estática e utilizar em diversos ponto

public static final DateTimeFormatter ANO_MES_FORMATTER = DateTimeFormatter. ofPattern("MMM/yyyy", new Locale("pt", "BR"));

Page 30: Migrando aplicações do mundo real para o java se 8

Date and Time● A API prover diversos métodos e formas para que seja efetuado cálculos

com data

YearMonth start = YearMonth.now();YearMonth end = YearMonth.now().plusMonths(1);

int days = ChronoUnit.DAYS.between(start.atDay(1), end.atEndOfMonth()).getDays();

Page 31: Migrando aplicações do mundo real para o java se 8

Date and Time● Métodos para comparações

YearMonth yearMonth = YearMonth.of(year, month);if (yearMonth.isAfter(YearMonth.now())) {

//processa data futura...}

Page 32: Migrando aplicações do mundo real para o java se 8

Date and Time (Hibernate)● Alguns lugares temos a persistência de LocalDate e LocalDateTime, como

persistir com JPA(Hibernate)? UserTypeinterface UserType { boolean isMutable();//não /** It is not necessary to copy immutable objects */ Object deepCopy (Object value);

/** should perform a deep copy if the type is mutable */ Serializable disassemble (Object value);

/** should perform a deep copy if the type is mutable */ Object assemble (Serializable cached, Object owner );

/** For immutable objects it is safe to simply return the first parameter */

Object replace (Object original, Object target, Object owner );}

Page 33: Migrando aplicações do mundo real para o java se 8

Date and Time (Hibernate)● Criar duas classes muito parecidas ou uma classe abstrata que

implementa o comportamento parecido das duas? Nenhuma; default methods

public interface ImmutableUserType extends UserType {@Override public default boolean equals(Object x, Object y) {

return Objects.equals(x, y); }

@Override public default int hashCode(Object x) { return Objects.hashCode(x);

}

@Override public default boolean isMutable() { return false;

}

...

Page 34: Migrando aplicações do mundo real para o java se 8

Date and Time (Hibernate)● E mais métodos:

...@Override public default Object deepCopy(Object value) {

return value; }

@Override public default Serializable disassemble(Object value) {return (Serializable) value;

}

@Override public default Object assemble(Serializable cached, Object owner) {

return cached; }

@Override public default Object replace(Object original, Object target, Object owner) { return original;

}}

Page 35: Migrando aplicações do mundo real para o java se 8

● LocalDateTimeTypepublic class LocalDateTimeType implements ImmutableUserType { @Override public int[] sqlTypes() {

return new int[] { Types.TIMESTAMP}; }

@Override public Class<LocalDateTime> returnedClass() { return LocalDateTime.class;}

@Override public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) { Timestamp persistValue = (Timestamp) rs.getObject(names[0]); if (rs.wasNull()) { return null; }

return persistValue.toLocalDateTime(); }

...

Date and Time (Hibernate)

Page 36: Migrando aplicações do mundo real para o java se 8

● LocalDateTimeType...

@Override public void nullSafeSet(PreparedStatement st, Object value,

int index, SessionImplementor session ) {if (value == null) { st.setNull(index, sqlTypes()[0]);} else {

Timestamp timestamp = Timestamp.valueOf((LocalDateTime) value);

st.setObject(index, timestamp, sqlTypes ()[0]);}

}}

Date and Time (Hibernate)

Page 37: Migrando aplicações do mundo real para o java se 8

Dificuldades

Page 38: Migrando aplicações do mundo real para o java se 8

Dificuldades

Page 39: Migrando aplicações do mundo real para o java se 8

Dificuldades

Page 40: Migrando aplicações do mundo real para o java se 8

Dificuldades● Nossos estressados membros do EG, especialmente nosso amigo Brian,

às vezes dão respostas "delicadas" ○ Porém ele pede desculpas em pvt depois, acreditem ou não :-)

● Nem sempre é muito fácil achar os métodos na API e precisa-se do apoio da lista○ Pelo menos eles respondem muito rápido!

● Alguns métodos que mostramos que existem na API hoje foram resultados dessas discussões○ Inclusive o getOrDefault, pro qual o Brian também deu uma resposta

delicada de primeira, mas tá aí agora● A API mudou de forma incompatível diversas vezes durante esse período,

fazendo com que às vezes perdêssemos 1 dia inteiro só para deixar tudo recompilando com lambda de novo :-(○ When you're living on the bleeding edge, you should not be surprised

when you do, in fact, bleed

Page 41: Migrando aplicações do mundo real para o java se 8

Formatação e estilo● A formatação e estilo do código afeta bastante a legibilidade (mais do que

nunca):List<String> emailsOrdenados = pessoas.stream().filter((Pessoa pessoa) -> pessoa.getDataNascimento ().isBefore(dezAnosAtras)).map((Pessoa pessoa)-> pessoa.getEmail()).sorted((String o1, String o2) -> o1 .compareToIgnoreCase (o2)).collect(Collectors.toList()) ;

● Versus:List<String> emailsOrdenados = pessoas.stream() .filter(pessoa -> pessoa.getDataNascimento ().isBefore(dezAnosAtras)) .map(Pessoa::getEmail) .sorted(String::compareToIgnoreCase ) .collect(toList());

Page 42: Migrando aplicações do mundo real para o java se 8

Suporte a Stream de Maps

Page 43: Migrando aplicações do mundo real para o java se 8

Suporte a Stream de Maps

NÃO TEM!

Page 44: Migrando aplicações do mundo real para o java se 8

Suporte a Stream de Maps● Foi discutido pelo EG, mas descartado por exigir classes específicas e ser

melhor suportado com tuplas● Tínhamos na nossa base vários casos funcionais de Map com Guava e

tivemos que converter para entrySet().stream()● É tão feio que não daria tempo de vocês entenderem na palestra (é sério!)● Vamos pensar seriamente se vale a pena manter na nossa base de código

com Java SE 8

Page 45: Migrando aplicações do mundo real para o java se 8

Stream para array● Como converter?● Stream.toArray(IntFunction<A[]> generator)

Pessoa[] p = pessoas.stream() //.filter .map ... .toArray((value) -> {//IntFunction > R apply(int value) //O que retornar? //new Pessoa[0] como em List.toArray?? //new Pessoa[10] acho que vai ter 10 ??? //new Pessoa[]{}; //java.lang.IndexOutOfBoundsException: does not fit });

Page 46: Migrando aplicações do mundo real para o java se 8

Stream para array● Como converter?● Stream.toArray(IntFunction<A[]> generator)

Pessoa[] p = pessoas.stream() //.filter .map ... .toArray((value) -> { return new Pessoa[value]; });

Page 47: Migrando aplicações do mundo real para o java se 8

Stream para array● Como converter?● Stream.toArray(IntFunction<A[]> generator)

Pessoa[] p = pessoas.stream() //.filter .map ... .toArray(Pessoa[]::new); //modo idiomático

Page 48: Migrando aplicações do mundo real para o java se 8

Acessos a recursos Java EE● Algumas APIs Java EE, direta ou indiretamente, acreditam que podem

controlar a instância "mágica" disponível via ThreadLocal (ex: FacesContext.getCurrentInstance())

● Com Lambda, elas falham miseravelmente com parallelStream()● Soluções?

○ Não usar parallelStream() :-(○ Criar na thread principal e sair passando○ Fazer patch do seu container preferido (se o seu container vem de

uma empresa de 3 letrinhas, ele é todo baseado em threads pra isso... boa sorte!)

○ Perturbar o Brian na lista para que haja uma SPI de criação do mecanismo de execução de parallelStream() (Michael já cansou de fazer isso... boa sorte!)

○ Parar de brincar com tecnologias não suportadas oficialmente :-)● Nós incluímos uma abstração no meio (porque o Janario tá com preguiça :

-p)

Page 49: Migrando aplicações do mundo real para o java se 8

Problemas - Spring● Spring(ASM) [SPR-10292]

○ O ASM não conseguia interpretar o bytecode gerado

java.lang.IllegalArgumentExceptionat org.springframework.asm.ClassReader.<init>(Unknown Source)

● Reportado pelo Michael em 13/02/2013● Solucionado 23/04/2013● Será lançado na versão 4.0 utilizamos em nossos testes a versão

snapshot. ● Nestes meses continuamos nossa migração validando pelos testes de

integração

Page 50: Migrando aplicações do mundo real para o java se 8

Problemas - Spring● Spring - JDK (DocumentBuilderFactory(b92))

○ No build 92 do JDK exista uma restrição de segurança que não permitia a requisição, durante a validação, de urls de namespace de xmls

org.xml.sax.SAXException: schema_reference: Failed to read schema document 'spring-beans-3.1.xsd', because 'http' access is not allowed.

● Soluções:○ System property -Djavax.xml.accessExternalSchema=all○ Chamada via api DocumentBuilderFactory.setAttribute("http://javax.

xml.XMLConstants/property/accessExternalSchema", "all");

● Não ocorre mais na última versão testada b97

Page 51: Migrando aplicações do mundo real para o java se 8

Problemas - Spring● Necessário utilizar o snapshot (enquanto não sair a versão final)

Page 52: Migrando aplicações do mundo real para o java se 8

Problemas - JBoss(Jandex)● Jandex (Java Annotation Indexer) JANDEX-14 - Um indexador de

anotações○ Não conseguia interpretar classes com bytecode que continham

expressões lambda (invokedynamic constant pool tag 18)java.lang.IllegalStateException: Unknown tag! pos=1 poolCount = 61at org.jboss.jandex.Indexer.processConstantPool(Indexer.java:603)

● Reportado pelo Janario em 16/05/2013● Pull request aceito 22/05/2013 (https://github.com/wildfly/jandex/pull/12) - Janario Oliveira

Page 53: Migrando aplicações do mundo real para o java se 8

Problemas - JBoss x JDK● ConcurrentSkipListSet - Ao adicionar os processors em um o mesmo fica

com chamadas infinitas ao compareTo do objeto adicionado.org.jboss.as.server.deployment.RegisteredDeploymentUnitProcessor.compareTo(RegisteredDeploymentUnitProcessor.java:41)org.jboss.as.server.deployment.RegisteredDeploymentUnitProcessor.compareTo(RegisteredDeploymentUnitProcessor.java:28)java.util.concurrent.ConcurrentSkipListMap.findPredecessor(ConcurrentSkipListMap.java:696)java.util.concurrent.ConcurrentSkipListMap.doPut(ConcurrentSkipListMap.java:843)java.util.concurrent.ConcurrentSkipListMap.putIfAbsent(ConcurrentSkipListMap.java:2325)java.util.concurrent.ConcurrentSkipListSet.add(ConcurrentSkipListSet.java:241)org.jboss.as.server.DeployerChainAddHandler.addDeploymentProcessor(DeployerChainAddHandler.java:60)

● Não sabemos se é um bug no JDK ou no JBoss

● Utilizamos a versão customizada neste ponto em específico para evitar este erro.

Page 54: Migrando aplicações do mundo real para o java se 8

Problemas - Lombok● Lombok

○ Issue #145 ainda em aberto desde 15/02/2013 :-(○ Processor do Lombok não é compatível com o JavaC do Java SE 8○ Incompatível com NetBeans 7.4, já que o JavaC do Java SE 8 é

utilizado pelo IDE para os parsings internos (Editor por exemplo)● Reportado 15/02/2013 - Jan Lahoda● Continua em aberto

● Apesar de não utilizarmos em nossos projetos, nosso amigo Michel Graciano utiliza.

Page 55: Migrando aplicações do mundo real para o java se 8

Performance

Page 56: Migrando aplicações do mundo real para o java se 8

Compilação

Page 57: Migrando aplicações do mundo real para o java se 8

Microbenchmark - Caliper● For each - AtomicInteger em uma lista

//ForEachClassicfor (Integer integer : list) { atomicInteger. accumulateAndGet(integer, Integer::sum);}

//ForEachStreamlist.stream().forEach((integer) -> { ... });

//ForEachArrayListlist.forEach((integer) -> {...});

//ForEachParallelStreamlist.parallelStream().forEach((integer) -> {...});

Page 58: Migrando aplicações do mundo real para o java se 8

Microbenchmark - CaliperFor each - AtomicInteger

Page 59: Migrando aplicações do mundo real para o java se 8

Microbenchmark - CaliperFor each - Fatorial em todos valores de 0 a 2000

private final IntFunction<Integer> factorial = i -> { return i == 0 ? 1 : i * factorial.apply(i - 1);};

//ForEachClassicfor (Integer integer : list) { factorial.apply(integer);}

//ForEachStreamlist.stream().forEach((integer) -> { ... });

//ForEachArrayListlist.forEach((integer) -> { ... });

//ForEachParallelStreamlist.parallelStream().forEach((integer) -> { ... });

Page 60: Migrando aplicações do mundo real para o java se 8

Microbenchmark - CaliperFor each - Fatorial

Page 61: Migrando aplicações do mundo real para o java se 8

Execução

Page 62: Migrando aplicações do mundo real para o java se 8

Conclusão● Migrar aplicações do mundo real para o Java SE 8 hoje é possível - se

você realmente souber Java e se elas tiverem testes● As novas funcionalidades podem realmente tornar seu código bem mais

legível● Ganhos de performance podem ser obtidos - mas sempre meça seu

código com ferramentas como Caliper, JMeter e um bom profiler● Vários métodos e novos idiomas aceleram o desenvolvimento● O Spring 4.0.0-SNAPSHOT *por enquanto* funciona, ao passo que o

JBoss, só com hacks ● Para facilitar a sua migração use Java 7 (pra começo de conversa), adote

o Guava e o backport da JSR 310 para Java 7 (https://github.com/ThreeTen/threetenbp)

● Siga as listas e participe ativamente das mesmas● Se você acha que seria capaz de fazer as coisas descritas nessa palestra

- e gostaria de ter tempo pago pela empresa para isso -, mande seu cv para [email protected] :-)

Page 63: Migrando aplicações do mundo real para o java se 8

Obrigado!Janario Oliveira | @janarioliverMichael Nascimento Santos | @mr_ _mMichel Graciano | @mgraciano

Page 64: Migrando aplicações do mundo real para o java se 8

Agradecimentos● Michel Graciano (@mgraciano) - Sem dúvida

uma grande ajuda na coleta e organização do conteúdo destes slides

Page 65: Migrando aplicações do mundo real para o java se 8

Q&AJanario Oliveira | @janarioliverMichael Nascimento Santos | @mr_ _mMichel Graciano | @mgraciano