Java 8: Lambda ou method reference? Entenda a diferença

O Java 8 trouxe algumas novas funcionalidades e dentre elas a possibilidade de usarmos lambdas e method references. Caso ainda não esteja familiarizado com esses recursos, talvez se interesse pelo nosso post de o mínimo que você deve saber sobre Java 8. As duas features são relacionadas e nos ajudam a reduzir a quantidade de código escrito, com uma abordagem um pouco mais funcional.

Para dar um exemplo de uso de cada uma delas, vamos supor que temos uma lista de nomes e que gostaríamos de imprimir seu conteúdo. Antes do lambda e method reference, o código ficaria dessa forma:

 List<String> nomes = Arrays.asList(“Lucas”, “Rodrigo”, “Paulo”);
  for(String nome : nomes) {
      System.out.println(nome);
  }

Que nos resultaria na saída:

Lucas
Rodrigo
Paulo

Esse mesmo código, com uso da expressão lambda e o método default forEach, poderia ficar assim, em uma única linha:

nomes.forEach(nome -> System.out.println(nome));

E daria para fazer o mesmo com o method reference, deixando ainda mais simples:

nomes.forEach(System.out::println);

Em todos os casos teríamos a mesma saída:

Lucas
Rodrigo
Paulo

Mas qual a real diferença entre essas abordagens? Qual das duas versões você prefere usar no seu dia a dia?

Existe uma opção melhor para todos os casos?

Ao perceber que as features fazem coisas parecidas é normal se perguntar qual delas é a melhor. A resposta seria depende, nenhuma delas é a melhor solução para todos os casos. Entender a diferença entre elas é fundamental para saber quando é o melhor momento de usar cada uma.

Podemos usar method reference sempre?

É importante ressaltar que nem sempre é possível substituir um lambda por um method reference. Para conseguir fazer uma chamada a um method reference é necessário que a invocação de método da direita receba os mesmos parâmetros da esquerda do lambda.

No exemplo abaixo temos a nossa lista de nomes e queremos criar uma nova lista apenas com apenas as primeiras 3 letras de cada nome.

List<String> nomes = Arrays.asList(“Lucas”, “Rodrigo”, “Alura”);

List<String> nomesReduzidos = nomes
    .map(nome -> nome.substring(0, 3))
    .collect(Collectors.toList());

Nesse caso não seria possível usar o method reference, uma vez que para chamar o método substring seria necessário passar um parâmetro externo, os números 0 e 3, que definem o início e fim da nova string.

Vantagens de se usar Method Reference

Apesar de não poder ser usado em todos os casos onde se usa a lambda, method references possuem algumas vantagens bem legais. A primeira delas está no fato de usar menos símbolos, o que facilita bastante a legibilidade do código.

Há uma outra vantagem, que é a facilidade de se entender o que está sendo manipulado, uma vez que o tipo do objeto fica sempre muito explícito. Para exemplificarmos: suponha que temos uma classe Pessoa e essa classe possui o método getName, utilizando lambda para chamar esse método faríamos algo do tipo:

 
p -> p.getName()

Veja que é necessário entender qual o contexto para poder entender o que é o *p*, enquanto com method reference, teríamos algo como

 
Pessoa::getName

Fica bem mais fácil identificar o elemento que está sendo manipulado, percebe? Está explicito.

Outros usos do method reference

Um uso para o method reference que nem sempre é de conhecimento geral é que ele também funciona em métodos que recebem mais de um parâmetro, contanto que a quantidade seja a mesma que a de atributo do objeto que estamos usando.

Imagine que temos um mapa de alunos e sua nota, e gostaríamos de passar isso para um outro mapa.

Usando lambdas, nosso código seria:

Map<String, Integer> novoMapaDeNotas = new HashMap<String, Integer>();
notasAlunos.forEach( (nome, nota) -> novoMapaDeNotas.put(nome, nota));

Veja que o lado esquerdo do lambda recebe dois parâmetros, o nome e a nota. O lado direito também recebe esses parâmetros, nessa mesma ordem. Que tal então fazer dessa forma, com method reference:

Map<String, Integer> novoMapaDeNotas = new HashMap<String, Integer>();
notasAlunos.forEach(novoMapaDeNotas::put);

E quanto a diferença de performance?

Pensando na questão de performance o lambda faz uma chamada de método a mais do que o method reference para executar o método, e as duas abordagens resultam em bytecodes diferentes, entretanto para a maioria dos casos essa diferença não chega a afetar performance. Só precisa ser levada em consideração em sistemas críticos onde a performance é essencial, não no forEach e manipulações simples do dia a dia.

E você, já tem usado Java 8 e esses recursos em seus projetos de produção? Aqui na Caelum e Alura usamos bastante. Tem até um curso dando uma visão bem legal sobre as novidades dessa versão.

6 Comentários

  1. Fernando Almeida 26/12/2016 at 17:28 #

    Muito bom. Imaginava já essa vantagem, mas não tinha lido nenhum artigo que explicitasse essa diferença. Ainda não utilizo o Java 8 organizacionalmente(limitações de ferramentas de terceiros), mas em projetos pessoais já aplico e começo a perceber as vantagens do mesmo.

  2. Rafael Gimenes Leite 27/12/2016 at 17:03 #

    Tenho estudado o java 8 para poder melhor uso do JavaFX, fiquei impressionado mas ainda receoso de usar esses tipos novos de implementações, e fiquei assutado ao saber que geram bytecodes diferentes.
    Desde o java5 com generics não evolui nem pro uso de listas sou meio arcaico e uso boa e velha array.
    Tentarei migrar pra esses novos conceitos, quem sabe um bom curso pra me reciclar.

  3. Helcio da Silva 01/01/2017 at 01:13 #

    Excelente post.

  4. Rafael 10/04/2017 at 01:30 #

    Muito bom

  5. Jonathan 17/08/2017 at 19:28 #

    Eu fiquei meio em dúvida em como usar os references com metodos com parametros.
    como no exemplo abaixo:
    //tenho uma lista de palavras e quero orderna las, como eu tornaria isso em um reference?

    palavras.sort((s1, s2) -> Integer.compare(s1.length(), s2.length()));

Deixe uma resposta