Introdução à Programação Funcional em Java
Por muito tempo, Java foi associada exclusivamente à programação orientada a objetos (POO). No entanto, com o lançamento do Java 8 em 2014, a linguagem deu um salto poderoso em direção ao paradigma funcional. Expressões lambda, interfaces funcionais e a Stream API mudaram radicalmente a abordagem de escrever código, tornando-o mais conciso, legível e menos propenso a erros.
A programação funcional em Java não substitui a POO, mas é um poderoso complemento. Ela permite escrever código declarativo, onde você descreve o que precisa ser feito, e não como. Neste artigo, vamos explorar os conceitos-chave que ajudarão você a dominar o estilo funcional em Java e mostrar exemplos práticos.
1. Expressões Lambda e Interfaces Funcionais
A base da programação funcional em Java são as expressões lambda — uma forma compacta de implementar funções anônimas. Sintaticamente, uma lambda consiste em uma lista de parâmetros, uma seta (->) e um corpo:
(parâmetros) -> { corpo }As lambdas sempre trabalham em conjunto com interfaces funcionais. Uma interface funcional é uma interface que contém exatamente um método abstrato (por exemplo, Runnable, Comparator, Predicate).
Exemplo Prático: Substituindo uma Classe Anônima
Vamos comparar a abordagem antiga (POO) com a nova (funcional):
// Forma antiga (classe anônima)Runnable taskOld = new Runnable() { @Override public void run() { System.out.println("Olá da classe anônima!"); }};
// Forma nova (lambda)Runnable taskNew = () -> System.out.println("Olá da lambda!");
// ExecuçãotaskNew.run();Como você pode ver, a lambda reduziu o código de 6 linhas para 1, mantendo a funcionalidade completa.
Interfaces Funcionais Embutidas (pacote java.util.function)
O Java fornece um rico conjunto de interfaces embutidas. Aqui estão as quatro principais:
Predicate<T>— recebe um objeto, retornaboolean. Usado para filtragem.Function<T, R>— recebe um objeto de um tipo, retorna um objeto de outro tipo. Para transformações.Consumer<T>— recebe um objeto, não retorna nada (void). Para efeitos colaterais (impressão).Supplier<T>— não recebe nada, retorna um objeto. Para geração preguiçosa de dados.
Exemplo com Predicate:
import java.util.function.Predicate;
public class Main { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; System.out.println(isEven.test(10)); // true System.out.println(isEven.test(7)); // false }}2. Stream API: Trabalhando com Coleções no Estilo Funcional
A Stream API é a joia da programação funcional em Java. Um fluxo (Stream) é uma sequência de elementos que suporta operações sequenciais e paralelas. A principal vantagem: você não modifica os dados originais, mas cria um novo resultado.
A Stream API é construída em três etapas:
- Criação do fluxo (a partir de uma coleção, array ou gerador).
- Operações intermediárias (filtragem, ordenação, transformação). Retornam um novo Stream.
- Operação terminal (coleta do resultado, contagem, busca). Finaliza o fluxo.
Exemplo: Filtragem e Transformação de uma Lista
Suponha que temos uma lista de strings e queremos obter apenas aquelas com mais de 3 caracteres, convertê-las para maiúsculas e ordená-las.
import java.u