_lambda Chegando ao Lambda no Java 8 Como melhorar seu código atual (e futuro) utilizando funções Lambda. Você sabe ordenar uma lista? Sabe selecionar os elementos de uma lista a partir de um critério? E extrair os valores de uma determinada propriedade do objeto de uma lista? São tarefas comuns que estamos cansados de repeti-las. Mas será que conhecemos a melhor forma de escrevê-las? Há como fazer de forma mais concisa, sem perder a legibilidade? Neste artigo vamos mostrar o que muda nessas tarefas do dia a dia ao utilizar bibliotecas como o Google Guava e LambdaJ, que adicionam um sabor funcional ao Java atual. Depois chegaremos ao futuro Java 8, para ver como o lambda e os defender methods estão mudando a linguagem em direção ao funcional. Paulo Silveira | [email protected] Bacharel e mestre em computação pela Universidade de São Paulo. Fundador do GUJ.com.br, trabalha com Java há 11 anos. É um dos responsáveis técnicos pelos cursos da Caelum. Raphael Lacerda | [email protected] Pós-graduado em Engenharia de Sistemas. Atua como analista de sistemas no Banco do Brasil e ministra cursos na Caelum Brasília. Entusiasta por expressividade de código e DSLs. Possui as certificações SCJP e SCWCD. É um dos responsáveis técnicos pelos cursos da Caelum. 37 \ P rogramação funcional é um tema em alta mesmo no mercado corporativo. Sua ascensão ficou bem apresentada pelo artigo de Steve Vinosky, “Welcome to the functional web”, e pode ser claramente vista pela grande quantidade de linguagens funcionais (ou ao menos com um pouco mais de características funcionais) na JVM. Scala e Clojure são expoentes aqui. Os artigos na MundoJ revelam também essa tendência. Java é orientada a objetos. O que poderíamos aproveitar do conceito do paradigma funcional para melhorar alguns pontos subjetivos do nosso código: legibilidade, expressividade e concisão? Usando apenas a API padrão, muito pouco. Ficamos reféns das longas e “verborrágicas” classes anônimas, ou de laços que se repetem exaustivamente. Como o Java e o JCP estão reagindo a essa tendência? Certamente a linguagem está bastante defasada, não só em relação às outras com pequena penetração no mercado corporativo, como Scala e Clojure, mas também do Ruby e Python, sem contar o C#, que já possui recursos funcionais muito avançados há bastante tempo. Para a oitava edição da plataforma, prevista para setembro de 2013, teremos a inclusão de uma sintaxe própria para definir lambdas, em estilo muito similar a essas linguagens. Veremos, neste artigo, como será essa nova sintaxe, junto com algumas novidades da JSR 335. Mas precisamos esperar ainda um ano para tirar proveito de uma API mais elaborada, além da sintaxe adocicada? Ao final deste artigo você conhecerá duas bibliotecas funcionais: Guava e LambdaJ. Elas possuem uma robusta API para facilitar o trabalho do dia a dia, em especial relacionado a coleções. O LambdaJ vai além: abusa das proxies dinâmicas para mimetizar uma sintaxe diferente. Você pode ler este artigo de maneira relativamente independente: mostraremos cada uma das abordagens para resolver problemas parecidos, envolvendo simples manipulações de coleções, utilizando a classe Palestrante. A Listagem 1 mostra essa classe, que implementa Comparable de forma trivial e possui, como atributos, o número de posts do palestrante no GUJ, um id e um nome. Listagem 1. Implementação de Palestra e Palestran- te. public class Palestrante implements Comparable<Palestrante> { private final int id; private final String name; private final int postsNoGuj; private boolean favoritado; / 38 } public void favoritar() { favoritado = true; } @Override public int compareTo(Palestrante o) { Objects.requireNonNull(o); return Integer.compare(this.id, o.getId()); } Algumas tarefas do dia a dia com o Java sem esteróides Vamos atacar os problemas triviais sem utilizar nenhuma biblioteca de fora do Java. Se temos uma lista de Palestrante chamada palestrantes, como costumamos imprimir todos seus nomes? Escrevemos um foreach: for (Palestrante p : palestrantes) { System.out.println(p); } Para somar todos seus posts, temos outro simples laço: int soma = 0; for (Palestrante p : palestrantes) { soma += p.getPostsNoGuj(); } Para filtrar todos os que já fizeram mais de 500 posts no GUJ, e depois favoritá-los, o código também é bastante simples: for (Palestrante p : palestrantes) { if (p.getPostsNoGuj() > 500) p.favoritar(); } Para orderná-los através de posts, criamos um Comparator. Podemos fazer isso em outra classe, mas é comum ficar numa classe anônima: Comparator<Palestrante> comparador = new Comparator<Palestrante>() { public int compare(Palestrante o1, Palestrante o2) { if (o1.getPostsNoGuj() < o2.getPostsNoGuj()) return -1; if (o1.getPostsNoGuj() > o2.getPostsNoGuj()) return 1; return 0; } }; Collections.sort(palestrantes, comparador); São quatro curtos códigos que estamos cansados de escrever no dia a dia. Como eles ficam adicionando um pouco de programação funcional? Será que torná-los curtos facilitaria a legibilidade? Melhoraria a forma de pensar? Google Guava O Guava é um apanhado geral de funcionalidades que os desenvolvedores do Google usam nos projetos Java da empresa. A biblioteca abrange várias áreas como coleções (inclusive criando outros tipos de collections como o MultiSet), concorrência, I/O, reflection, validação, dentre outros. Eles resolveram juntar todos esses utilitários em um projeto só, dando origem ao Guava. Está disponível para quem faz gerenciamento de dependências com o Maven, Gradle, Ivy e Buildr e tem uma boa documentação. A própria biblioteca diz que para tentar atingir uma programação funcional utilizando Java 7 é só através de um código estranho e muito verboso, basicamente utilizando classes anônimas, portanto ela traz funcionalidades para reverter esse quadro. O projeto contém algumas classes e interfaces que ajudam na programação no estilo funcional como Function, Predicate, FluentIterable e uma classe utilitária Collections2. Em suma, podemos fazer uma operação básica como filtragem com praticamente uma combinação de todas elas, o que muda é que, por exemplo, a classe FluentIterable provê essa funcionalidade seguindo o padrão FluentInterface. Como primeiro exemplo, a partir de uma lista de participantes, deve-se obter uma lista dos nomes em maiúsculo. Para isso vamos utilizar os métodos da classe FluentIterable from e trasform. System.out.println(FluentIterable.from(palestrantes) .transform(new Function<Palestrante, String>() { @Override public String apply(Palestrante palestrante) { return palestrante.getName().toUpperCase(); } })); Comparators. Ela possui alguns métodos utilitários para ordenação, como levar em conta valores null, ordem reversa, ordem lexicográfica, verificar se a lista está ordenada, dentre outros. Vamos fazer três exemplos rápidos de ordenação. Primeiramente ordenar uma lista desordenada de participantes pela sua ordem natural (definida pela interface Comparable). List<Palestrantes> palestrantesOrdenadosPorId = Ordering.natural().sortedCopy(palestrantesDesordenados); Depois vamos ordenar por nome, para isso vamos criar utilizar a classe Function. Function<Palestrante, String> ordernacaoPorNome = new Function<Palestrante, String>() { @Override public String apply(Palestrante p) { return p.getName(); }}; List<Palestrante> palestrantes =Ordering.natural(). onResultOf(ordernacaoPorNome). sortedCopy(palestrantes); Por fim ordená-los por nome e posts no guj, onde nomeComparator e postsComparator representam referências para classes anônimas que implementam a interface Comparator. List<Palestrante> palestrantesNomePosts = Ordering.from(nomeComparator).compound( postsComparator).sortedCopy(palestrantes); Concluindo, a principal vantagem do Guava em relação à API nativa do Java é que ele possui alguns métodos de suporte mais efetivos, entretanto criar classes anônimas Function e Predicate não muda muito em quantidade/expressividade de código. Agora vamos para um problema mais comum. A LambdaJ LambdaJ é uma bibioteca para manipular colepartir de uma lista de palestrantes, obter somente ções de uma forma mais simples e natural. Foi criada aqueles que tiveram mais do que 500 posts. por Mario Fusco e surgiu devido à necessidade de enSystem.out.println(FluentIterable.from(palestrantes).filter( tender melhor uma parte do domínio que lidava com new Predicate<Palestrante>() { interações em coleções em um projeto que ele parti@Override cipava. A biblioteca foi desenhada como sendo uma public boolean apply(Palestrante palestrante) { DSL interna para o Java. Segundo Fusco, esse código return palestrante.getPostsNoGuj() > 500; era complexo, repetitivo e de difícil leitura. Os desen} volvedores gastavam mais tempo tentando adivinhar })); o que um determinado loop fazia do que escrevendoExtraindo métodos e utilizando imports estáti- -o. E ele afirma: “...códigos como este são feitos para o computador ler e não o programador...”. Bom, a sicos, conseguimos um código mais sucinto: tuação parece familiar para você? Ela é muito fácil de se utilizar, tem uma boa doSystem.out.println(from(palestrantes).filter( comPostsMaiorQue500())); cumentação e basta ir ao site do projeto para baixá-la, inclusive já possui um jar único com todas as dePara fazer funções de ordenação, vamos usar a pendências e está disponível também para quem faz classe Ordering, que é um comparador fluente e pode o gerenciamento com o Maven. Na parte técnica, ela ser usado para manipular, estender e fazer uso de faz muito uso de proxy e reflexão, o que a torna mais 39 \ expressiva que as anteriores, todavia um pouco menos eficiente com relação ao desempenho. Recomenda-se verificar na página do projeto a sua análise de desempenho e a questão toda pode se resumir a um trade-off, perdendo um pouco no desempenho para ganhar na expressividade de código. Sua principal classe é a Lambda, que contém uma série de métodos (funções) estáticos. Inicialmente podemos imprimir o nome de todos os participantes, para isso vamos utilizar a função joinFrom. Podemos fazer um pequeno refactoring para torná-lo mais legível: List<String> nomesMaiusculos = convert(nomeDosPalestrantes, paraMaiusculo()); Temos agora o seguinte problema, mais comum no dia a dia do desenvolvedor. A partir de uma lista de palestrantes, selecionar os palestrantes com mais de 500 posts e colocá-los como favoritos. Já sabemos como é essa solução usando Java puro, ocupando algumas linhas de código. Já com LambdaJ tudo se resolve com apenas uma linha. Para isso vamos utilizar System.out.println(joinFrom(palestrantes).getName()); o método select para selecionar os participantes e o método forEach para percorrer essa lista e favoritáO import estático do método joinFrom foi feito -los. Também iremos usar o método having e greapara facilitar a leitura. Agora vamos ordenar os par- terThanOrEqualTo, ambos do Hamcrest, uma depenticipantes a partir da quantidade de posts, para isso dência do LambdaJ. vamos utilizar o método sort. List<Palestrante> participantesOrdenadosPorPost = sort(palestrantes, on(Palestrante.class). getPostsNoGuj()); forEach(select(palestrantes,having(on( Palestrante.class).getPostsNoGuj(), greaterThanOrEqualTo(500)))).favoritar(); Poderia ter sido usado o método and e or para adicionar outras restrições. Agora a leitura ficou fáO item de comparação é obtido a partir do métocil, todavia ainda pode ser melhorada, tornar-se mais do on. Podemos obter uma expressividade melhor ao expressiva. Vamos extrair a parte de comparação para extrair a comparação para um método privado. um método privado e parametrizar o valor a ser comparado. List<Palestrante> participantesOrdenadosPorPost = sort(palestrantes, porPostsNoGuj()); A classe Lambda possui método de agregação como avg, max e min. Exemplo: forEach(select(palestrantes, comPostsMaiorQue(500))).favoritar(); O LambdaJ também possui uma classe LambdaCollection que segue o padrão Fluent Interface assim como o FluentIterable do Guava. Vamos utilizá-la no seguinte problema: tendo uma lista de palestras Agora a partir de uma lista de participantes, va- (considere a nova classe Palestra abaixo), selecionar mos colocar todos os nomes em maiúsculo. Primeiro as palestras que tiveram uma votação superior a 100, vamos extrair o nome dos participantes. Para isso, então a partir desta selecionar os seus palestrantes ordenados por quantidade de posts. Para isso vamos será usado o método extract. utilizar os métodos with, que devolve uma referência List<String> nomesDosPalestrantes = para uma LambdaCollection, retain, que tem funcioextract(palestrantes, on(Palestrante.class). namento análogo ao select e o extract que já vimos getName()); previamente: int totalDePosts = sumFrom(palestrantes). getPostsNoGuj(); Com o nome dos participantes, o objetivo agora é colocá-los em letra maiúscula. Para isso vamos usar o método convert e a interface do LambdaJ Converter. Ela especifica como converter um objeto de um tipo em outro. No exemplo será de String para String. List<String> nomesMaiusculos = convert( nomeDosPalestrantes, new Converter<String, String>(){ @Override public String convert(String nome){ return nome.toUpperCase(); }}); / 40 public class Palestra { private final String titulo; private final Palestrante palestrante; private final int quantidadeDeVotos; // getters e constructor omitidos } List<Palestrante> palestrantes = with(palestras). retain(having(on(Palestra.class). getQuantidadeDeVotos(), greaterThanOrEqualTo(100))). extract(on(Palestra.class).getPalestrante()). sort(on(Palestrante.class).getPostsNoGuj()); mais programação funcional na MundoJ/ > Na edição número 52 da MundoJ, Bruno Kioshita fez uma excelente introdução sobre paradigma funcional, explicando conceitos como Functions, Predicates, dentre outros. Também abordou como fazer o uso da biblioteca Apache Functor, que possui ideias parecidas com as bibliotecas que serão descritas neste artigo. Por isso, iremos focar essencialmente na prática de como utilizar funções em Java, atualmente com bibliotecas externas e futuramente com o Lambda no Java 8. > Já na edição 53, Leandro Moreira fez um artigo de introdução à linguagem Clojure, remetendo a alguns conceitos de Programação Funcional. Nesta mesma edição Hugo Ferreira descreve vários problemas que podem ser resolvidos com programação Objeto-Funcional e, por fim, Rafael Ferreira traz o recurso de pattern matching da linguagem funcional Scala. Novamente, com uma refatoração, temos: ter sido adicionado. E no Java 8 ele foi, dentro da interface Iterable. List<Palestrante> palestrantes = with(palestras). Isso é bastante perigoso: evoluir uma interface retain(comQuantidadeDeVotosMaiorQue(100)). adicionando novos métodos quebram classes exisextract(palestrante()).sort(porPostsNoGuj()); tentes que a implementavam! Como o time fez isso, Por fim, recomendamos olhar nas referências minimizando os riscos? Eles adicionaram suporte aos para verificar as outras funcionalidades da biblioteca, extension methods. Se você olhar o código-fonte da alguns métodos como index, group e project podem interface Iterable, encontrará o seguinte método: resolver problemas de uma forma mais concisa do void forEach(Block<? super T> block) default { que a sua atual solução. Por exemplo, em um cenário Iterables.forEach(this, block); de aplicações distribuídas, em que há a necessidade } de se serializar objetos para transferi-los entre JVMs diferentes, o padrão DTO pode ser utilizado. Então Agora você pode criar métodos em interfaces no podemos chegar ao seguinte código, caso queiramos Java e colocar uma implementação default, desoinstanciar objetos do tipo DadosPessoais que tam- brigando suas implementadoras de o reescreverem. bém possuam o atributo nome: Algo parecido com os mixins do Ruby e com os traits do Scala, mas ainda sem a possibilidade de conter List<DadosPessoais> palestrantesParaTransferencia = atributos não-estáticos. A palavra-chave default, project(palestrantes,DadosPessoais. dentro de um método de interface, é quem faz essa class,on(Palestrante.class). mágica e define-o como extension method. Classes getId(),on(Palestrante.class).getName()); que implementam Iterable não precisarão reescrever esse método. Há muitas interfaces e classes novas no Java 8 nos Lambda no Java 8 pacotes lang e util. Iterables, Comparators, MapStreApesar de estar planejado apenas para setembro am, BiVal/BiValue são apenas algumas delas, além do de 2013, você já pode utilizar milestones do JDK8, pacote java.util.functions, onde a Block se encontra. disponíveis para os principais sistemas operacionais, Trabalharemos com elas no nosso dia a dia e vamos inclusive para o Mac OSX. O time do lambda do JDK utilizar algumas delas nessa seção. 8 é liderado por Brian Goetz, muito conhecido por seu livro de programação concorrente na plataforma palestrantes.forEach(p -> { System.out.println(p); }); Java. Direto ao código. Dada uma List<Palestrante> Apesar de trazer muitas novidades, um lambda palestrantes, como imprimir todos eles? não pode ser criado de qualquer maneira. O código palestrantes.forEach(new Block<Palestrante>() { abaixo não compila: }); public void apply(Palestrante p) { System.out.println(p); } Quem conhece um pouco da API de coleções sabe que não há um método forEach dentro de List, nem em suas principais superinterfaces: Collection e Iterable. Para esse código funcionar, além dessa interface Block precisar existir, esse método forEach precisa Object o = p -> { System.out.println(p); }; Uma expressão lambda precisa sempre ser atribuída/inferida para uma interface/class abstrata que possui apenas um único método abstrato (antigamente chamadas de SAM: single abstract method). A especificação, ainda em andamento, as chamam agora de functional interfaces. Por exemplo, para um Runnable, conseguimos inferir um lambda que não 41 \ recebe argumentos: Runnable r = () -> { System.out.println(); }; Iterable<Integer> posts = palestrantes.map(Palestrante::getPostsNoGuj); Dado que temos os inteiros, podemos somá-los Como o compilador sabe que esse conteúdo vai para dentro do método run()? Pois é o único método com o reduce (similar ao foldLeft ou inject em outras abstrato da interface. Assim como o método apply(T linguagens): o) é o único da interface Block<T>. Integer total = posts.reduce(0, (total, proximo) Podemos ir mais adiante. Quando o lambda vai -> total+proximo); realizar apenas uma única invocação de método com O reduce recebe o valor inicial da conta, para detodos os parâmetros recebidos, podemos criá-lo atrapois aplicar, item a item, o lambda dado. Esse lambda vés de uma referência para um método: possui dois parâmetros e será inferido a uma implepalestrantes.forEach(System.out::println); mentação da nova interface BinaryOperator. E para filtrar quem tem mais de 500 posts no Assustador? O compilador vai inferir que o Block GUJ? Passamos um lambda para ser utilizado como a ser passado para o forEach vai ser uma invocação implementação do método test da nova interface do método println em cima de System.out, passando Predicate: o único parâmetro que esse Block pode receber: um palestrante. Se você tivesse passado uma referência palestrantes.filter(p -> p.getPostsNoGuj() > 500); para um método que recebe mais de um parâmetro, o compilador não conseguiria inferir o lambda! Se você quer invocar o método favoritar em cada Vale lembrar que isso não é um “method lite- um desses palestrantes, basta utilizar o forEach que ral”, da mesma forma que temos class literals (como já conhecemos: String.class, por exemplo). Fazer Method m = System. palestrantes.filter(p -> p.getPostsNoGuj() > 500). out::println, ou algo semelhante, não compila. Uma forEach(Palestrante::favoritar); referência a um método nada mais é que outra forma de escrever um lambda, então possui as mesmas Quer finalmente ordená-los? Finalmente há o restrições: há necessidade de uma interface funcional método sort na interface list! envolvida na expressão. palestrantes.sort((p1, p2) -> …. ) Assim como o forEach, há muitos novos extensions methods na interface Iterable, conhecidos dos Passando seu Comparator como argumento, poprogramadores funcionais: map, reduce, filter, grou- dendo ser inferido por um lambda. pBy, mapped etc. Para pegar o número de posts que Com esses novos recursos, o código Java se aprocada Palestrante tem no GUJ, fazemos: xima ao de linguagens como Scala e Ruby. O mesmo código em Java, Scala e Ruby, sem abusar muito de Iterable<Integer> posts = palestrantes.map( outros recursos da linguagem (como o underscore do p -> p.getPostsNoGuj()); Scala), ficaria: Nesse caso nem precisamos usar as chaves depois palestrantes.map(Palestrante::getPostsNoGuj). dos parâmetros. Não precisamos utilizá-los quando reduce(0, (n, x) -> n+x); o conteúdo do método é uma instrução de return. O palestrantes.map(_.postsNoGuj).fold(0)(n,x => n+x) mesmo ocorre com os parênteses dos parâmetros, palestrantes.map(&:postsNoGuj).inject(0){|n,x| n+x} que são opcionais no caso de ser apenas um. Em outras palavras, poderíamos escrever: Esse foi apenas um sucinto começo do que está por vir. Vale conhecer todos os métodos dentro de Iterable<Integer> posts = palestrantes.map((p) Iterable, assim como as interfaces do java.util.func-> { return p.getPostsNoGuj(); } ); tions e as novas superinterfaces das collections. Há Ou ainda podemos usar a notação de method re- ainda references para construtores, novos métodos ference: em classes importantes do Java 8 e pequenas melhorias por toda API. Será um grande passo para a pla- / mais projetos: Apache Collections > Existe também o já conhecido projeto Apache Collections, que possui algumas novas interfaces e classes utilitárias para manipular coleções. / 42 /eu uso Geraldo Ferraz | [email protected] Analista desenvolvedor da Cast e ministra cursos na Caelum Brasília. No projeto em que trabalho, chegamos no ponto em que melhorar legibilidade e expressividade tornou-se uma prioridade. Por muito tempo o projeto permaneceu com muitos loops e estruturas de condições encadeadas, e por mais que extraíssemos métodos não conseguíamos ler o código e saber de primeira o que estava sendo feito. Com o LambdaJ conseguimos melhorar todos os aspectos citados, entretanto, adicionamos bastante complexidade e alta dependência da API. Com alguma refatoração é possível deixar o código de forma que a complexidade fique encapsulada tornando o uso muito mais intuitivo. /referências para saber mais/ > “Herança e Composição – os princípios por trás dos Para baixar os builds do JDK8: padrões”, ed. 39 da MundoJ – Eduardo Guerra > http://openjdk.java.net/projects/jdk8/ > “JPA 2: Os novos recursos inspirados no Hibernate”, ed. > http://openjdk.java.net/projects/lambda/ 39 da MundoJ – Paulo Silveira e Raphael Lacerda > “Enums desmistificadas”, ed. 26 da MundoJ – Alexandre Gazola > Série “Design patterns para um Mundo Real”, eds. 21, 22 e 23 da MundoJ – Rodrigo Yoshima Para ver a evolução do lambda no JDK8, conheça sua antiga sintaxe e propostas: > http://blog.caelum.com.br/trabalhando-com-closuresno-java-8/ > http://www.javac.info/ Para conhecer mais do LambdaJ: > http://blog.caelum.com.br/codigo-expressivo-eprogramacao-funcional-em-java-com-lambdaj taforma. Ao mesmo tempo, alguns outros itens ainda ficaram de fora, como a sintaxe simplificada para criação de coleções e literais para membros da classe. Considerações finais Utilizar o Guava e o LambdaJ pode trazer diversos ganhos a sua aplicação. Mas será que o código é mais expressivo? Possui mais abstração? É mais conciso? São critérios de difícil definição e há bastante discussão a respeito. Deixamos links na referência sobre esse assunto. Mesmo que seu código esteja agora mais expressivo, será que essas novas bibliotecas não serão uma barreira grande para novos programadores da equipe? É certamente um ponto que deve ser considerado ao adotar APIs como essas. Assim como o impacto de performance que o LambdaJ pode trazer por causa do uso de muitas dynamic proxies, esses são trade-offs que devem ser estudados. E quando começar a utilizar o Java 8? Apesar de já haver milestones sólidos, a API está mudando bastante, além de que não há IDE nenhuma com suporte a nova sintaxe. É provável que, no início do ano de 2013, tanto Eclipse quanto Netbeans ofereçam suporte. > http://code.google.com/p/lambdaj/wiki/LambdajFeatures DSLs, interfaces fluentes e outros patterns envolvidos aqui: > http://martinfowler.com/dsl.html > http://martinfowler.com/bliki/FluentInterface.html > http://blog.jayway.com/2012/02/07/builder-patternwith-a-twist/ Conhecendo mais do Guava: > http://code.google.com/p/guava-libraries/ Expressividade, abstração e concisão: > http://www.joelonsoftware.com/items/2006/08/01.html > http://gafter.blogspot.com.br/2007/03/on-expressivepower-of-programming.html Um pouco de teoria sobre o cálculo lambda e programação funcional: > http://steve.vinoski.net/pdf/IC-Welcome_to_the_ Functional_Web.pdf > http://blog.caelum.com.br/comecando-com-o-calculolambda-e-a-programacao-funcional-de-verdade/ 43 \