Resposta rápida
Expressões lambda em Java são uma forma curta de representar uma função anônima. Na prática, elas servem para deixar o código mais limpo quando você precisa passar comportamento como argumento, principalmente com interfaces funcionais, Stream API e callbacks.
Se você já usa Java 8 ou superior, vale muito a pena dominar lambdas porque elas reduzem boilerplate, deixam filtros e mapeamentos mais legíveis e aparecem o tempo todo em código moderno.
List<String> nomes = Arrays.asList("Ana", "Bruno", "Carla");
nomes.forEach(nome -> System.out.println(nome));O que são expressões lambda em Java
Em Java, uma lambda é uma maneira concisa de escrever uma implementação pequena e pontual de um comportamento. Em vez de criar uma classe anônima inteira para uma ação simples, você escreve diretamente a lógica no ponto de uso.
Isso ficou disponível a partir do Java 8 e mudou bastante a forma de trabalhar com coleções, eventos, validações e operações funcionais.
Antes e depois
Antes das lambdas
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Botão clicado!");
}
});Com lambda
button.addActionListener(e -> System.out.println("Botão clicado!"));O ganho aqui não é só estético. Em código real, isso reduz repetição e facilita leitura quando o foco é a regra de negócio, não a estrutura da linguagem.
Sintaxe das expressões lambda
A forma básica é esta:
(parâmetros) -> expressão ou blocoCasos comuns
Sem parâmetros
Runnable tarefa = () -> System.out.println("Executando...");Um parâmetro
Consumer<String> logger = mensagem -> System.out.println("Log: " + mensagem);Mais de um parâmetro
Comparator<String> comparador = (a, b) -> a.compareTo(b);Com bloco
Function<String, Integer> tamanho = texto -> {
System.out.println("Calculando tamanho de: " + texto);
return texto.length();
};Quando o corpo da lambda tem mais de uma linha, você usa chaves e return se precisar devolver algo.
Interfaces funcionais em Java
Lambda só funciona bem quando há uma interface funcional, ou seja, uma interface com um único método abstrato. É isso que dá ao Java um alvo para a implementação da expressão.
Interfaces mais usadas
| Interface | Serve para | Método | Exemplo prático |
|---|---|---|---|
Function<T, R> | Transformar um valor em outro | R apply(T t) | String para Integer |
Consumer<T> | Executar ação sem retorno | void accept(T t) | Imprimir ou gravar log |
Supplier<T> | Fornecer valor | T get() | Gerar valor padrão |
Predicate<T> | Testar condição | boolean test(T t) | Filtrar dados |
BiFunction<T, U, R> | Receber dois valores e retornar um resultado | R apply(T t, U u) | Somar ou combinar valores |
Exemplo real com interfaces padrão
Function<String, Integer> converter = Integer::parseInt;
System.out.println(converter.apply("123")); // 123
Predicate<String> naoVazio = texto -> texto != null && !texto.isBlank();
System.out.println(naoVazio.test("Java")); // true
Consumer<String> imprimir = texto -> System.out.println("Mensagem: " + texto);
imprimir.accept("Processo finalizado");
Supplier<LocalDate> hoje = LocalDate::now;
System.out.println(hoje.get());Interface funcional própria
@FunctionalInterface
public interface Calculadora {
int calcular(int a, int b);
}
Calculadora soma = (a, b) -> a + b;
Calculadora multiplica = (a, b) -> a * b;
System.out.println(soma.calcular(10, 5));
System.out.println(multiplica.calcular(10, 5));Exemplos práticos de expressões lambda
Aqui é onde as lambdas realmente fazem diferença no dia a dia. Veja situações comuns em projetos Java.
Ordenar uma lista
List<String> nomes = Arrays.asList("João", "Maria", "Pedro", "Ana", "Carlos");
nomes.sort((a, b) -> a.compareTo(b));
System.out.println(nomes); // [Ana, Carlos, João, Maria, Pedro]Filtrar valores
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> pares = numeros.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(pares); // [2, 4, 6, 8, 10]Processar dados de forma mais legível
List<String> produtos = Arrays.asList("mouse", "teclado", "monitor");
produtos.stream()
.map(String::toUpperCase)
.forEach(System.out::println);Evento em interface gráfica
JButton botao = new JButton("Salvar");
botao.addActionListener(e -> System.out.println("Salvando dados..."));Executar uma tarefa em thread
new Thread(() -> {
System.out.println("Processamento iniciado");
}).start();Lambda com Stream API
O uso mais comum de expressões lambda em Java hoje é com a Stream API. Ela combina bem com lambdas porque permite criar pipelines de processamento mais claros.
Exemplo completo
List<String> nomes = Arrays.asList("João", "Maria", "Pedro", "Ana", "Juliana", "Júlio");
List<String> resultado = nomes.stream()
.filter(nome -> nome.startsWith("J"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println(resultado); // [JÚLIO, JULIANA, JOÃO]Quando usar parallelStream
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
long somaPares = numeros.parallelStream()
.filter(n -> n % 2 == 0)
.mapToLong(Integer::longValue)
.sum();
System.out.println(somaPares); // 30Importante: parallelStream nem sempre é mais rápido. Ele faz sentido quando há volume de dados e a operação é realmente adequada para paralelismo.
Erro comum ao usar lambdas
Um erro frequente é tentar alterar uma variável externa dentro da lambda ou assumir que qualquer interface pode receber lambda.
Exemplo de problema
int total = 0;
List<Integer> numeros = Arrays.asList(1, 2, 3);
numeros.forEach(n -> total += n); // erro: total não é efetivamente finalEm vez disso, use uma abordagem funcional com redução:
int total = numeros.stream()
.mapToInt(Integer::intValue)
.sum();Outro erro comum
List<String> nomes = Arrays.asList("Ana", "Bruno");
nomes.sort((a, b) -> { a.compareTo(b); }); // falta returnSe a lambda usa bloco com chaves, lembre-se de retornar o valor explicitamente:
nomes.sort((a, b) -> {
return a.compareTo(b);
});Bloco prático: refatorando código legado
Vamos pegar um cenário bem comum: filtrar e exibir clientes ativos.
Versão tradicional
List<Cliente> ativos = new ArrayList<>();
for (Cliente cliente : clientes) {
if (cliente.isAtivo()) {
ativos.add(cliente);
}
}
for (Cliente cliente : ativos) {
System.out.println(cliente.getNome());
}Versão com lambda e Stream API
clientes.stream()
.filter(Cliente::isAtivo)
.map(Cliente::getNome)
.forEach(System.out::println);Esse tipo de refatoração deixa o fluxo mais direto: filtra, transforma e consome. Em time, isso costuma facilitar manutenção e revisão de código.
Melhores práticas
Para usar expressões lambda em Java sem perder clareza, siga estas recomendações:
Mantenha a lambda pequena
Se a lógica está crescendo demais, mova para um método nomeado. Lambda boa é lambda que resolve uma intenção simples.
Prefira method references quando fizer sentido
lista.forEach(System.out::println);Evite efeitos colaterais desnecessários
Em vez de preencher listas externas manualmente, use map, filter e collect.
Nomeie bem o que não for óbvio
Se a lambda vira uma regra importante do negócio, talvez mereça virar um método com nome claro.
Perguntas frequentes
1. Toda lambda em Java precisa de interface funcional?
Sim. A expressão lambda precisa de um alvo compatível, normalmente uma interface funcional com um único método abstrato.
2. Lambda substitui classe anônima em todos os casos?
Não. Ela substitui muito bem casos simples, mas classes anônimas ainda podem ser úteis quando você precisa de mais estrutura, estado ou comportamento mais complexo.
3. Expressões lambda deixam o código mais rápido?
Nem sempre. O maior ganho é legibilidade e produtividade. Em performance, o resultado depende do contexto, da JVM e da forma como você usa streams e coleções.
4. Quando usar lambda e quando usar method reference?
Use method reference quando a lambda só chama um método existente. Use lambda quando houver alguma lógica extra ou transformação específica.
5. Posso usar lambda com qualquer versão do Java?
Não. Lambdas foram introduzidas no Java 8. Em versões anteriores, isso não existe.
Próximos passos
Se você quer fixar bem o assunto, o melhor caminho é praticar com código real:
- refatore um
forsimples parastream(); - troque uma classe anônima por lambda;
- use
filter,mapecollectem uma lista do seu projeto; - compare lambda e method reference no mesmo exemplo.
Depois disso, vale avançar para Stream API, method references e programação funcional em Java, porque esses temas aparecem juntos na prática.