Programação Funcional em Java: de A a Z
Programação Funcional (PF) é um paradigma onde os cálculos são tratados como avaliação de funções matemáticas, evitando mudanças de estado e dados mutáveis. O Java, tradicionalmente uma linguagem imperativa, a partir da versão 8, incorpora ativamente elementos de PF. Isso permite escrever código mais conciso, previsível e facilmente testável, especialmente ao trabalhar com coleções e operações assíncronas.
Neste artigo, abordaremos os conceitos-chave de PF em Java: desde expressões lambda até técnicas avançadas de trabalho com Stream API e Optional. Você aprenderá como aplicar essas ferramentas na prática para que seu código se torne mais limpo e eficiente.
1. Fundamentos: Expressões Lambda e Interfaces Funcionais
Expressão lambda é uma forma concisa de escrever um método anônimo (função). Ela permite passar comportamento como dado. Sintaxe: (parâmetros) -> corpo.
Interface funcional é uma interface com um único método abstrato (SAM — Single Abstract Method). São essas interfaces que podem ser implementadas com lambdas. As principais interfaces embutidas em java.util.function:
Predicate<T>— recebe um objeto T, retorna boolean (métodotest).Function<T, R>— recebe T, retorna R (métodoapply).Consumer<T>— recebe T, não retorna nada (métodoaccept).Supplier<T>— não recebe nada, retorna T (métodoget).UnaryOperator<T>— caso especial de Function, onde T e R são do mesmo tipo.
Exemplo: Ordenação de lista com Comparator (interface funcional)
import java.util.*;
public class LambdaExample { public static void main(String[] args) { List<String> names = Arrays.asList("Ivan", "Anna", "Pedro", "Maria");
// Sem lambda (classe anônima) Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } });
// Com lambda Collections.sort(names, (a, b) -> a.length() - b.length());
// Ainda mais curto: usando Comparator.comparingInt names.sort(Comparator.comparingInt(String::length));
System.out.println(names); // [Anna, Pedro, Ivan, Maria] }}Exemplo: Uso de Predicate para filtragem
import java.util.function.Predicate;
public class PredicateExample { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isEven.test(4)); // true System.out.println(isEven.and(isPositive).test(-2)); // false (não positivo) System.out.println(isEven.or(isPositive).test(-3)); // true (não é par, mas é positivo? não, -3 não é positivo) }}2. Stream API: Poderosa ferramenta de processamento de dados
Stream API é uma sequência de elementos que suporta operações paralelas e sequenciais. Stream não armazena dados, mas os transforma. As principais operações são divididas em intermediárias (retornam um novo Stream) e terminais (finalizam o processamento e retornam um resultado ou efeito colateral).
Principais operações do Stream API:
- Intermediárias:
filter,map,flatMap,sorted,distinct,peek,limit,skip. - Terminais:
collect