Expressões Lambda em Java
Introduzidas no Java 8, as expressões lambda revolucionaram a forma como escrevemos código, permitindo-nos tratar funcionalidades como se fossem dados. Uma expressão lambda é, essencialmente, um bloco de código anônimo que pode ser armazenado em uma variável, passado como argumento para métodos ou retornado por eles.
A base de uma expressão lambda é o operador ->
(seta), que a divide em duas partes:
- À esquerda: A lista de parâmetros que o bloco de código recebe.
- À direita: O corpo da expressão, onde as instruções são executadas.
Uma expressão lambda não funciona sozinha. Ela serve para fornecer uma implementação concreta e concisa para uma interface funcional.
O que é uma Interface Funcional?
Uma interface funcional é uma interface que declara apenas um único método abstrato. Ela atua como um "molde" para a expressão lambda.
É considerada uma excelente boa prática anotar essas interfaces com @FunctionalInterface
. Esta anotação não é obrigatória, mas instrui o compilador a verificar se a interface realmente segue a regra de ter apenas um método abstrato. Isso previne erros acidentais e deixa a intenção do código mais clara para outros desenvolvedores.
Vamos ver um exemplo prático:
// 1. Definição da Interface Funcional
@FunctionalInterface
interface Operationable {
int calculate(int x, int y);
}
public class LambdaApp {
public static void main(String[] args) {
// 2. Declaração de uma variável do tipo da interface
Operationable operation;
// 3. Atribuição de uma expressão lambda à variável
operation = (x, y) -> x + y;
// 4. Chamada do método através da interface
int result = operation.calculate(10, 20);
System.out.println(result); // 30
}
}
Neste exemplo, a expressão (x, y) -> x + y
fornece a implementação para o método calculate
da interface Operationable
.
Lambda vs. Classe Anônima
Antes do Java 8, a mesma funcionalidade seria alcançada com uma classe anônima, que é muito mais verbosa. O código a seguir é funcionalmente idêntico ao exemplo com lambda:
Operationable op = new Operationable() {
@Override
public int calculate(int x, int y) {
return x + y;
}
};
int z = op.calculate(20, 10);
System.out.println(z); // 30
Fica claro como a expressão lambda simplifica drasticamente a sintaxe.
Sintaxe dos Parâmetros
A sintaxe dos parâmetros em uma lambda é flexível:
Os tipos dos parâmetros podem ser omitidos, pois o compilador os infere a partir da interface funcional.
// Ambas as formas são válidas Operationable op1 = (x, y) -> x + y; Operationable op2 = (int x, int y) -> x + y;
Se houver apenas um parâmetro, os parênteses podem ser omitidos.
// (n) -> n * n; é o mesmo que: n -> n * n;
Se não houver parâmetros, os parênteses vazios são obrigatórios.
() -> 30 + 20;
Corpo da Expressão Lambda
O corpo de uma lambda pode ser uma única expressão ou um bloco de código.
Expressão de Linha Única
Se o corpo tiver apenas uma instrução, o resultado dessa instrução é retornado automaticamente (sem a necessidade de return
ou chaves {}
).
// Retorna a soma de x e y
(x, y) -> x + y;
Bloco de Código
Se o corpo tiver múltiplas instruções, ele deve ser envolvido por chaves {}
. Se o método da interface esperar um retorno, a palavra-chave return
deve ser usada explicitamente.
Operationable operation = (int x, int y) -> {
if (y == 0) {
return 0; // Retorno explícito
} else {
return x / y; // Retorno explícito
}
};
System.out.println(operation.calculate(20, 10)); // 2
System.out.println(operation.calculate(20, 0)); // 0
Lambdas e o Escopo de Variáveis
Expressões lambda podem "capturar" variáveis de seu escopo circundante.
Variáveis de Classe (Estáticas ou de Instância)
Podem ser livremente acessadas e modificadas dentro de uma lambda.
public class LambdaApp {
static int x = 10;
static int y = 20;
public static void main(String[] args) {
Operation op = () -> {
x = 30; // Modificação permitida
return x + y;
};
System.out.println(op.calculate()); // 50
System.out.println(x); // 30 (o valor foi alterado)
}
}
interface Operation {
int calculate();
}
Variáveis Locais (de Método)
Podem ser acessadas, mas não podem ser modificadas pela lambda. A variável local deve ser "efetivamente final", o que significa que seu valor não pode ser alterado após a inicialização, nem dentro nem fora da lambda.
public static void main(String[] args) {
int n = 70;
int m = 30;
Operation op = () -> {
// n = 100; // ERRO DE COMPILAÇÃO! Não pode modificar variável local.
return m + n;
};
System.out.println(op.calculate()); // 100
// n = 100; // ERRO DE COMPILAÇÃO! Tornaria 'n' não-efetivamente final.
}
Interfaces Funcionais Genéricas
Uma interface funcional pode ser genérica. Ao criar a lambda, você especifica o tipo, e o compilador infere os tipos para os parâmetros.
@FunctionalInterface
interface Operationable<T> {
T calculate(T x, T y);
}
public class LambdaApp {
public static void main(String[] args) {
// Implementação para Integer
Operationable<Integer> op1 = (x, y) -> x + y;
System.out.println(op1.calculate(20, 10)); // 30
// Implementação para String
Operationable<String> op2 = (x, y) -> x + y;
System.out.println(op2.calculate("20", "10")); // "2010"
}
}
Resumo
- Expressões lambda são blocos de código anônimos que implementam o único método de uma interface funcional.
- O operador
->
separa a lista de parâmetros do corpo da expressão. - A sintaxe é flexível, permitindo omitir tipos de parâmetros e parênteses.
- O corpo pode ser uma única expressão (com retorno implícito) ou um bloco de código em chaves (com
return
explícito). - Lambdas podem capturar variáveis, mas só podem modificar variáveis de classe, não as locais.