DEV Community

Cover image for Boas práticas ao usar Lambda e classe anônima em Java
Carolina Dias Fonseca
Carolina Dias Fonseca

Posted on

Boas práticas ao usar Lambda e classe anônima em Java

Já faz algum tempo que venho falando sobre classes locais, classes anônimas e expressões lambdas. O que são, como usar e quando usar. No atual post vou escrever sobre boas práticas ao usar lambda em Java.

Devo usar tudo o que a linguagem tem?

Acho relevante colocar aqui que apesar do Java ter muitos recursos que podem e realmente ajudam no dia a dia de uma pessoa desenvolvedora, não quer dizer exatamente que esses recursos ajudem a manter um controle e facilidade de leitura/alteração no momento da manutenção do código.

Portanto, quando falamos em “boas práticas” é focado no momento em que a aplicação foi para produção e, dali pra frente, teremos as manutenções, revisões, novas releases e etc. Do meu ponto de vista, sempre temos que pensar que a manutenção não necessariamente será realizada por outra pessoa, mas sim que pode ser qualquer pessoa, incluindo quem criou a aplicação, portanto, é importante pensarmos:

Qual a melhor forma de deixar esse código simples não só para alterações, mas de leitura e entendimento futuro?

Claro que não podemos da performance, segurança, arquitetura e etc, mas sempre mantendo em mente a questão de carga cognitiva de compreensão do código. Afinal, ninguém quer passar uma semana só para tentar entender o que se passa diante dos seus olhos ali na IDE ou então ficar debugando linha a linha para tentar entender o que aquele trecho de código faz ou deixa de fazer.

Vai por mim, já tive que fazer isso porque dei de cara com uma aplicação sem o mínimo de documentação e muito menos com código legível e compreensível. Só para dar uma noção, conseguimos achar um dos responsáveis pela a aplicação e nem ele sabia explicar o que ele e/ou o time tinham feito, então... acho que deu para entender, né?

E como faz?

Pensando nisso, tenho lido muito sobre design pattern (ou, em português, padrões de projeto) tanto da Gang of Four - GoF (ou, em português, Gangue dos Quatro), como o SOLID e, o mais recente nas minhas leituras, o Effective Java (Java Efetivo) do Joshua Bloch.

Esse último, até onde encontrei, está em sua 3ª edição e fala das melhores práticas em Java, usando o Java 9. Questionei no Twitter sobre a relação versão atual do Java (estamos na 18) versus a edição desse livro e a galera mais sênior em relação a mim, super recomendaram a leitura e adicionaram que, para o universo Java, é um dos livros que mais ajudam a entender como otimizar o código, simplificá-lo, além de dar uma visão mais ampla do próprio Java.

Dessa forma, o que Bloch fala sobre Lambdas é, basicamente o seguinte:

Use expressão lambda ao inveś de classe anônima

Agora vamos ao porquê: Classe anônima é mais verbosa, ao passo que a expressão lambda é semelhante a função dentro da classe anônima, porém bem mais concisa.

Veja abaixo:

//Exemplo usando classe anônima
1 Collections.sort(palavras, new Comparator<String>() {
2    public int compare(String palavra1, String palavra2) {
3        return Integer.compare(palavra1.length(), palavra2.length());
4    }
5 });

//Exemplo usando expressão lambda
6 Collections.sort(palavras,
7 (palavra1, palavra2) -> Integer.compare(palavra1.length(), palavra2.length()));
Enter fullscreen mode Exit fullscreen mode

Na expressão lambda e em grande maioria das vezes, o compilador será capaz de deduzir os tipos especificados na linha 2 da classe anônima por um processo chamado por inteferência de tipo, portanto, podemos reduzir ainda mais o exemplo de expressão lambda:

//Exemplo usando classe anônima
1 Collections.sort(palavras, new Comparator<String>() {
2    public int compare(String palavra1, String palavra2) {
3        return Integer.compare(palavra1.length(), palavra2.length());
4    }
5 });

//Exemplo 1 usando expressão lambda
6 Collections.sort(palavras,
7 (palavra1, palavra2) -> Integer.compare(palavra1.length(), palavra2.length()));

//Exemplo 2 usando expressão lambda em sua versão mais concisa
8 palavras.sort(comparingInt(String::length));
Enter fullscreen mode Exit fullscreen mode

Outra dica muito importante que o Bloch passa ainda sobre essa questão de lambdas é de sempre que possível priorizar o uso de métodos genéricos, pois assim diminuirá as chances do compilador dar erro por inferência de tipo.

Por exemplo, as enums facilitam muito o uso interfaces funcionais e lambdas exatamente pelo conceito de métodos mais genéricos.

//Exemplo de uso com Enum sem interface funcional
1 public enum Operacao {
2   SOMA("+") {
3       public double apply(double x, double y) { return x + y; }
4   },
5   SUBTRACAO("-") {
6       public double apply(double x, double y) { return x - y; }
7   },
8   MULTIPLICACAO("*") {
9       public double apply(double x, double y) { return x * y; }
10  },
11  DIVISAO("/") {
12      public double apply(double x, double y) { return x / y; }
13  };

14  private final String simbolo;

//Construtor da enum
15 Operacao(String simbolo) { 
16  this.simbolo = simbolo; 
17 }

//Pelas boas práticas de Bloch, sempre dar o override no toString
18 @Override public String toString() { 
19  return simbolo; 
20 }

//método abstrato para cada implementação
21 public abstract double apply(double x, double y);

22 }
Enter fullscreen mode Exit fullscreen mode

Do jeito que o código está acima, para cada constante será dado um override no método apply afinal, cada operação precisa de uma ação diferente para o mesmo método.

Agora veja como isso fica mais conciso, ou seja, reduzido e simples ao usarmos lambda:

1 public enum Operacao {
2   SOMA("+", (x, y) -> x + y),
3   SUBTRACAO("-", (x, y) -> x - y),
4   MULTIPLICACAO("*", (x, y) -> x * y),
5   DIVISAO("/", (x, y) -> x / y);

6 private final String simbolo;

7 private final DoubleBinaryOperator operacao;

//construtor da enum
8 Operation(String simbolo, DoubleBinaryOperator operacao) {
9       this.simbolo = simbolo;
10      this.operacao = operacao;
11 }

12 @Override public String toString() { 
13  return simbolo; 
14 }

/**o que antes era um método abstrato se torna um método concreto e 
* passamos a utilizar a interface funcional DoubleBinaryOperator cuja a ação é
* receber dois dados e receber um.
*/
15  public double apply(double x, double y) {
16      return operacao.applyAsDouble(x, y);
17  }
18 }
Enter fullscreen mode Exit fullscreen mode

Então vamos esquecer as classes abstrastas e classes anônimas?

Admito que o uso de classe abstrata ainda é algo um pouco confuso para mim se você for pensando em SOLID, por exemplo e até mesmo no próprio Java Efetivo, porém, gosto do ponto que o Bloch trás em seu livro quando diz o seguinte:

“[...] Lambdas are limited to functional interfaces. If you want to create an instance of an abstract class, you can do it with an anonymous class, but not a lambda. Similarly, you can use anonymous classes to create instances of interfaces with multiple abstract methods.”

Que, em tradução livre, nas mais é do que o uso de lambdas se restrigem ao uso de interfaces funcionais (algo que a própria documentação do Java reforça) e diz que a classe abstrata você utiliza numa classe anônima e NÃO em um lambda. Da mesma forma que você utiliza a classe anônima para criar instâncias de interfaces com múltiplos métodos.

Fontes: Documentação Java

Effective Java - Joshua Bloch

Discussion (1)

Collapse
luccaprado profile image
Lucca Prado

Gostei muito do "Devo usar tudo o que a linguagem tem?"
Pois me lembrou a discussão do uso do EVAL do JS.

No geral, artigo muito bom! Parabéns!