Expressões Lambda em Java: guia prático com exemplos reais e Stream API

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 bloco

Casos 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

InterfaceServe paraMétodoExemplo prático
Function<T, R>Transformar um valor em outroR apply(T t)String para Integer
Consumer<T>Executar ação sem retornovoid accept(T t)Imprimir ou gravar log
Supplier<T>Fornecer valorT get()Gerar valor padrão
Predicate<T>Testar condiçãoboolean test(T t)Filtrar dados
BiFunction<T, U, R>Receber dois valores e retornar um resultadoR 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); // 30

Importante: 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 final

Em 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 return

Se 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 for simples para stream();
  • troque uma classe anônima por lambda;
  • use filter, map e collect em 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.

Deixe um comentário