Este documento apresenta três palestrantes e sua experiência com Java. Ele discute a migração de aplicações do mundo real para Java SE 8, incluindo recursos como lambdas, data e hora, e como aprofundar o uso destes recursos. Algumas dificuldades na migração, como falta de suporte a streams de mapas, também são abordadas.
Migrando aplicações do mundo real para o java se 8
1. Migrando aplicações do
mundo real para o Java
SE 8
Janario Oliveira | @janarioliver
Michael Nascimento Santos | @mr_ _m
Michel Graciano | @mgraciano
2. 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
3. ● 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
4. ● 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
5. 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
9. 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
10. 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
11. JSR 310: Date and Time
● Imutável e thread-safe
● Utilize sempre as classes mais específicas para o problema
● YearMonth - Mês e ano
YearMonth.of(2013, Month.JULY);
● LocalDate - Data sem hora ou time-zone
LocalDate.now();
LocalDate dataTDC = LocalDate.of(2013, Month.JULY, 12);
● LocalTime - Hora sem data ou time-zone
LocalTime meiaNoite = LocalTime.MIDNIGHT;
LocalTime onzeHoras = LocalTime.of(11, 0);
assert meiaNoite.isBefore(onzeHoras);
12. 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-zone
OffsetDateTime. of(dataTDCMeioDia, ZoneOffset. ofHours(-3));
● ZonedDateTime - Data com hora e time-zone
ZonedDateTime. of(dataTDCMeioDia, ZoneId.of( "America/Sao_Paulo" ));
13. 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
14. JSR 335:
Lambda Expressions
● Permite programação funcional, com maior nível de reutilização de código
e escrita concisa
int 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 );
}
15. JSR 335:
Lambda Expressions
● Permite programação funcional, com maior nível de reutilização de código
e escrita concisa
pessoas.stream()
.filter(pessoa -> pessoa.getSexo() == Sexo.MASCULINO)
.mapToInt(Pessoa::getIdade)
.max()
.ifPresent(PessoaProcessor ::trataIdade);
16. JSR 335:
Lambda Expressions
● Permite programação funcional, com maior nível de reutilização de código
e escrita concisa
pessoas.parallelStream()
.filter(pessoa -> pessoa.getSexo() == Sexo.MASCULINO)
.mapToInt(Pessoa::getIdade)
.max()
.ifPresent(PessoaProcessor ::trataIdade);
18. 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
19. 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
20. 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
21. 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 :-)
22. 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?
23. 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
24. Tentando aprofundar o uso
dos novos recursos
● Como gerar um Map<Long,Brand>?
//Padrão throwingMerger: java.lang.IllegalStateException: Duplicate key
Map<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
25. 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));
26. 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());
27. Tentando aprofundar o uso
dos novos recursos
● Novos métodos úteis em Map:
//Java 7
Long l = totalByYearMonth.get(yearMonth);
long total = l == null ? 0L : l;
//Java 8
long total = totalByYearMonth. getOrDefault(yearMonth, 0L);
28. Tentando aprofundar o uso
dos novos recursos
● Novos métodos úteis em Map:
//Java 7
Map<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 8
totalByBrandByYearMonth
.putIfAbsent(yearMonth, new HashMap<>())
.merge(brand, total, Long::sum);
29. 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"));
30. 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();
31. Date and Time
● Métodos para comparações
YearMonth yearMonth = YearMonth.of(year, month);
if (yearMonth.isAfter(YearMonth.now())) {
//processa data futura...
}
32. Date and Time (Hibernate)
● Alguns lugares temos a persistência de LocalDate e LocalDateTime, como
persistir com JPA(Hibernate)? UserType
interface 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 );
}
33. 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;
}
...
34. 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;
}
}
35. ● LocalDateTimeType
public 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)
36. ● 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)
40. 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
41. 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());
44. 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
45. 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
});
46. Stream para array
● Como converter?
● Stream.toArray(IntFunction<A[]> generator)
Pessoa[] p = pessoas.stream()
//.filter .map ...
.toArray((value) -> {
return new Pessoa[value];
});
47. Stream para array
● Como converter?
● Stream.toArray(IntFunction<A[]> generator)
Pessoa[] p = pessoas.stream()
//.filter .map ...
.toArray(Pessoa[]::new); //modo idiomático
48. 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)
49. Problemas - Spring
● Spring(ASM) [SPR-10292]
○ O ASM não conseguia interpretar o bytecode gerado
java.lang.IllegalArgumentException
at 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
50. 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
51. Problemas - Spring
● Necessário utilizar o snapshot (enquanto não sair a versão final)
52. 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 = 61
at 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
53. 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.
54. 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.
62. 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 recrutamento@tecsinapse.com.br :-)