Lambdas como Parâmetros e Resultados de Métodos
A verdadeira força das expressões lambda em Java é a capacidade de tratar comportamento como se fosse um dado. Isso significa que podemos passar um bloco de código como argumento para um método ou até mesmo retornar um bloco de código como resultado de um método, abrindo portas para um estilo de programação muito mais flexível e poderoso.
1. Lambdas como Parâmetros: Injetando Comportamento
Imagine que você precisa de um método que some os números de um array, mas apenas os números que atendem a um certo critério (pares, ímpares, maiores que 5, etc.). Uma abordagem seria criar vários métodos: sumEvens()
, sumOdds()
, sumGreaterThan5()
.
Uma abordagem muito melhor é criar um único método sum
que recebe o critério de soma como um parâmetro. É exatamente isso que as expressões lambda nos permitem fazer.
import java.util.function.Predicate;
public class LambdaApp {
public static void main(String[] args) {
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// Cenário 1: Queremos somar apenas os números pares.
// A lambda (n -> n % 2 == 0) representa a "regra" para ser par.
System.out.println("Soma dos pares: " + sum(numbers, n -> n % 2 == 0));
// Cenário 2: Queremos somar apenas os números maiores que 5.
// Passamos uma regra diferente para o mesmo método.
System.out.println("Soma dos > 5: " + sum(numbers, n -> n > 5));
}
// Este método é flexível: ele aceita qualquer regra que retorne true/false para um inteiro.
private static int sum(int[] numbers, Predicate<Integer> condition) {
int result = 0;
for (int i : numbers) {
// Aplica a "regra" (a lambda) recebida como parâmetro.
if (condition.test(i)) {
result += i;
}
}
return result;
}
}
Nota: Em vez de criar nossa própria interface funcional, usamos a interface
java.util.function.Predicate<T>
, que já existe no Java e é perfeita para este caso: seu métodotest()
recebe um objeto e retornaboolean
.
O método sum()
é poderoso porque sua lógica principal (somar números) é separada da lógica de decisão (quais números somar). A lógica de decisão é injetada em tempo de execução através da expressão lambda.
2. Referências a Métodos (Method References)
Às vezes, a lógica que você quer passar em uma lambda já existe em um método em algum lugar do seu código. Em vez de reescrever a lógica em uma nova lambda, você pode passar uma referência direta para esse método. Isso torna o código ainda mais limpo e legível.
A sintaxe é NomeDaClasse::nomeDoMetodo
.
import java.util.function.Predicate;
// Classe com métodos utilitários
class NumberUtils {
public static boolean isEven(int n) {
return n % 2 == 0;
}
}
public class LambdaApp {
public static void main(String[] args) {
int[] numbers = { 1, 2, 3, 4, 5, 6 };
// Em vez de escrever (n -> n % 2 == 0), passamos uma referência
// direta ao método que já faz exatamente isso.
System.out.println("Soma dos pares: " + sum(numbers, NumberUtils::isEven));
}
// O método sum() é o mesmo do exemplo anterior.
private static int sum(int[] numbers, Predicate<Integer> condition) {
// ...
}
}
Para que uma referência de método seja válida, a assinatura do método (parâmetros e tipo de retorno) deve ser compatível com a do método da interface funcional.
Outros Tipos de Referências a Métodos
- Métodos de Instância:
objetoDaClasse::nomeDoMetodo
- Construtores:
NomeDaClasse::new
(útil para criar "fábricas" de objetos).
3. Lambdas como Resultados de Métodos: Fábricas de Funções
Um método também pode retornar uma expressão lambda. Isso permite criar "fábricas de funções", onde um método seleciona e retorna o comportamento desejado com base em algum critério.
import java.util.function.IntBinaryOperator;
public class LambdaApp {
public static void main(String[] args) {
// Pede ao método 'getOperation' para nos dar a função de soma.
IntBinaryOperator operacaoDeSoma = getOperation('+');
int resultado1 = operacaoDeSoma.applyAsInt(6, 5);
System.out.println("6 + 5 = " + resultado1); // 11
// Pede a função de multiplicação e a executa imediatamente.
int resultado2 = getOperation('*').applyAsInt(8, 2);
System.out.println("8 * 2 = " + resultado2); // 16
}
// Este método é uma "fábrica de operações". Ele retorna uma lambda.
private static IntBinaryOperator getOperation(char symbol) {
switch (symbol) {
case '+': return (x, y) -> x + y;
case '-': return (x, y) -> x - y;
case '*': return (x, y) -> x * y;
default: return (x, y) -> 0;
}
}
}
Nota: Aqui usamos a interface funcional
java.util.function.IntBinaryOperator
, que representa uma operação com dois operandosint
que retorna umint
.
O método getOperation()
não executa nenhum cálculo. Sua única função é retornar o comportamento (a lambda) que será executado mais tarde.
Resumo
- Lambdas como Parâmetros: Permitem criar métodos flexíveis e reutilizáveis, cuja lógica de decisão pode ser "injetada" em tempo de execução.
- Referências a Métodos: São uma forma concisa e legível de passar um comportamento que já existe em um método, sem precisar de uma nova lambda.
- Lambdas como Resultados: Permitem criar "fábricas" que constroem e retornam diferentes comportamentos com base em certas condições.