migrando aplicações do mundo real para o java se 8
DESCRIPTION
Slides tdc2013 - Trilha JavaTRANSCRIPT
Migrando aplicações do mundo real para o Java SE 8Janario Oliveira | @janarioliverMichael Nascimento Santos | @mr_ _mMichel Graciano | @mgraciano
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
● 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
● 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
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
DisclaimerNosso código e testes foram realizados com o b97(30/06/2013) do Lambda
#java8mundoreal
Introdução rápidaIntrodução aos principais conceitos e tecnologias do 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
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
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);
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" ));
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
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 );}
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);
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);
Migrando aplicações para 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
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
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
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 :-)
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?
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
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
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));
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());
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);
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);
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"));
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();
Date and Time● Métodos para comparações
YearMonth yearMonth = YearMonth.of(year, month);if (yearMonth.isAfter(YearMonth.now())) {
//processa data futura...}
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 );}
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;
}
...
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;
}}
● 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)
● 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)
Dificuldades
Dificuldades
Dificuldades
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
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());
Suporte a Stream de Maps
Suporte a Stream de Maps
NÃO TEM!
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
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 });
Stream para array● Como converter?● Stream.toArray(IntFunction<A[]> generator)
Pessoa[] p = pessoas.stream() //.filter .map ... .toArray((value) -> { return new Pessoa[value]; });
Stream para array● Como converter?● Stream.toArray(IntFunction<A[]> generator)
Pessoa[] p = pessoas.stream() //.filter .map ... .toArray(Pessoa[]::new); //modo idiomático
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)
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
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
Problemas - Spring● Necessário utilizar o snapshot (enquanto não sair a versão final)
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
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.
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.
Performance
Compilação
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) -> {...});
Microbenchmark - CaliperFor each - AtomicInteger
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) -> { ... });
Microbenchmark - CaliperFor each - Fatorial
Execução
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] :-)
Obrigado!Janario Oliveira | @janarioliverMichael Nascimento Santos | @mr_ _mMichel Graciano | @mgraciano
Agradecimentos● Michel Graciano (@mgraciano) - Sem dúvida
uma grande ajuda na coleta e organização do conteúdo destes slides
Q&AJanario Oliveira | @janarioliverMichael Nascimento Santos | @mr_ _mMichel Graciano | @mgraciano