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()));
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));
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 }
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 }
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
Top comments (1)
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!