Vídeos novos: segunda, quarta e sexta · 8h

Expressões Lambda em Java: guia prático com exemplos

Entenda a sintaxe, quando usar e como as lambdas deixam o seu código mais limpo.

As expressões lambda chegaram no Java 8 e mudaram a forma de escrever código na linguagem. Elas permitem passar comportamento como se fosse um valor: em vez de criar uma classe só para implementar um método, você escreve a função diretamente onde ela é usada.

Antes das lambdas

Para ordenar uma lista de nomes pelo tamanho, era preciso uma classe anônima:

List<String> nomes = new ArrayList<>(List.of("Ana", "Bernardo", "Lu")); Collections.sort(nomes, new Comparator<String>() { @Override public int compare(String a, String b) { return Integer.compare(a.length(), b.length()); } });

Com uma lambda, o mesmo código fica assim:

nomes.sort((a, b) -> Integer.compare(a.length(), b.length()));

A sintaxe

Uma lambda tem três partes: os parâmetros, a seta -> e o corpo.

// sem parâmetros Runnable r = () -> System.out.println("Olá!"); // um parâmetro: os parênteses são opcionais Consumer<String> imprimir = texto -> System.out.println(texto); // vários parâmetros BinaryOperator<Integer> somar = (a, b) -> a + b; // corpo com várias linhas: use chaves e return Function<Integer, String> classificar = n -> { if (n < 0) return "negativo"; return n == 0 ? "zero" : "positivo"; };

Interfaces funcionais

Uma lambda sempre implementa uma interface funcional: uma interface com um único método abstrato. O pacote java.util.function já traz as mais usadas:

InterfaceRecebeDevolveExemplo
Predicate<T>Tbooleann -> n > 18
Function<T, R>TRs -> s.length()
Consumer<T>Tnadas -> System.out.println(s)
Supplier<T>nadaT() -> new ArrayList<>()
BinaryOperator<T>T, TT(a, b) -> a * b

Você também pode criar as suas. A anotação @FunctionalInterface é opcional, mas faz o compilador avisar se alguém adicionar um segundo método abstrato:

@FunctionalInterface interface Desconto { double aplicar(double valor); } Desconto blackFriday = valor -> valor * 0.7; System.out.println(blackFriday.aplicar(100)); // 70.0

Method references

Quando a lambda só chama um método que já existe, dá para encurtar ainda mais com :::

nomes.forEach(nome -> System.out.println(nome)); nomes.forEach(System.out::println); // equivalente nomes.stream().map(String::toUpperCase); // método de instância Supplier<List<String>> nova = ArrayList::new; // construtor

Lambdas com Streams

É com a API de Streams que as lambdas mostram o seu valor. Filtrar, transformar e agrupar dados vira uma sequência de passos legíveis:

record Aluno(String nome, double nota) {} List<Aluno> alunos = List.of( new Aluno("Ana", 9.0), new Aluno("Caio", 5.5), new Aluno("Bia", 7.8)); List<String> aprovados = alunos.stream() .filter(a -> a.nota() >= 7) .sorted(Comparator.comparing(Aluno::nota).reversed()) .map(Aluno::nome) .toList(); System.out.println(aprovados); // [Ana, Bia]

Cuidados

  • Variáveis externas precisam ser efetivamente finais. Uma lambda pode ler uma variável local de fora, mas não alterá-la.
  • Lambdas longas escondem a lógica. Se passou de três ou quatro linhas, extraia para um método com nome e use method reference.
  • Exceções checadas não combinam bem com as interfaces de java.util.function: trate a exceção dentro da lambda ou crie a sua própria interface.
Resumo: lambdas são funções curtas que implementam uma interface funcional. Use-as para passar comportamento — em ordenações, eventos, threads e, principalmente, streams.