Pular para o conteúdo

Programação Funcional em Java

A Programação Funcional é um paradigma de programação que trata o código como uma sequência de transformações de dados, em vez de uma sequência de comandos que alteram o estado de objetos.

Analogia: imagine uma linha de produção. Em vez de um operador que empurra cada peça manualmente, temos esteiras que filtram, modificam e ordenam as peças automaticamente, até chegarem ao produto final.

No Java, esse paradigma é viabilizado por dois recursos:

  • Lambdas: funções anônimas e compactas.
  • Streams: sequências de elementos que podem ser processadas com operações funcionais.

Uma lambda é uma função anônima (sem nome) com uma sintaxe compacta:

Runnable r1 = new Runnable() { // classe anônima (forma tradicional)
public void run() {
System.out.println("Olá!");
}
};
Runnable r2 = () -> System.out.println("Olá!"); // lambda

A sintaxe é (parâmetros) -> corpo:

  • Sem parâmetros: () -> System.out.println("Olá!")
  • Com um parâmetro: nome -> nome.toUpperCase()
  • Com vários parâmetros: (a, b) -> a + b
  • Com corpo de várias linhas: (a, b) -> { ... }

O método forEach percorre todos os elementos de uma coleção e executa uma ação para cada um:

List<String> nomes = List.of("Ana", "Bruno", "Carla");
// Sem forEach
for (String nome : nomes) {
System.out.println(nome);
}
// Com forEach (lambda)
nomes.forEach(nome -> System.out.println(nome));

Quando a lambda apenas chama um método, podemos usar uma sintaxe ainda mais curta: a method reference (Classe::método):

nomes.forEach(System.out::println); // nome -> System.out.println(nome)
List<String> maiusculas = nomes.stream()
.map(String::toUpperCase) // nome -> nome.toUpperCase()
.toList();

Method references são muito comuns no código Java moderno e deixam as expressões bem mais legíveis.

Um Stream é uma sequência de elementos que pode ser processada com operações funcionais. Criamos um stream a partir de uma coleção com list.stream().

As operações se dividem em dois grupos:

  • Intermediárias: transformam o stream e retornam outro stream (map, filter, sorted, distinct).
  • Terminais: produzem o resultado final (toList(), collect(), forEach, count).
List<Integer> numeros = List.of(3, 1, 4, 1, 5);
numeros.stream().distinct().sorted().toList(); // [1, 3, 4, 5]

map aplica uma função a cada elemento e gera um novo stream com os resultados:

List<String> nomes = List.of("ana", "bruno", "carla");
List<String> maiusculas = nomes.stream()
.map(nome -> nome.toUpperCase())
.toList();
// [ANA, BRUNO, CARLA]

filter mantém apenas os elementos que satisfazem uma condição (boolean):

List<Integer> numeros = List.of(1, 2, 3, 4, 5, 6);
List<Integer> pares = numeros.stream()
.filter(numero -> numero % 2 == 0)
.toList();
// [2, 4, 6]

sorted() ordena os elementos (usando a ordem natural) e distinct() elimina duplicatas:

numeros.stream().sorted().distinct().toList(); // [1, 3, 5, 8]

Para ordenar objetos sem ordem natural, usamos um Comparator escrito como lambda:

// Ordena por nota, da maior para a menor
alunos.sort((a1, a2) -> Double.compare(a2.getNota(), a1.getNota()));
// O mesmo comportamento com Comparator
alunos.sort(Comparator.comparing(Aluno::getNota).reversed());

As operações terminais encerram o pipeline e produzem o resultado:

long total = numeros.stream().count(); // conta elementos
numeros.stream().forEach(System.out::println); // percorre o stream
// collect: versão clássica do toList()
List<Integer> lista = numeros.stream()
.collect(Collectors.toList());

toList() (Java 16+) retorna uma lista imutável; collect(Collectors.toList()) retorna uma lista mutável.

reduce combina todos os elementos em um único valor:

List<Integer> numeros = List.of(1, 2, 3, 4, 5);
int soma = numeros.stream().reduce(0, (a, b) -> a + b); // 15

Para valores numéricos, existem atalhos prontos:

int total = numeros.stream().mapToInt(n -> n).sum();
double media = numeros.stream().mapToInt(n -> n).average().orElse(0);
boolean alguemPassou = notas.stream().anyMatch(nota -> nota >= 6);
boolean todosPassaram = notas.stream().allMatch(nota -> nota >= 6);
Optional<Aluno> primeiro = alunos.stream().findFirst();
  • anyMatch: verifica se algum elemento satisfaz a condição.
  • allMatch: verifica se todos os elementos satisfazem a condição.
  • findFirst/findAny: retorna um elemento, sempre dentro de um Optional.
List<Aluno> alunos = List.of(
new Aluno("Ana", 8.5),
new Aluno("Bruno", 5.0),
new Aluno("Carla", 7.0)
);
// Nomes dos alunos aprovados, em ordem alfabética
List<String> aprovados = alunos.stream()
.filter(aluno -> aluno.getNota() >= 6)
.map(Aluno::getNome)
.sorted()
.toList();
// [Ana, Carla]

Neste tópico você viu que a programação funcional no Java é feita com lambdas e a API de Streams:

  • forEach percorre coleções e streams;
  • Method references (Classe::método) encurtam lambdas que apenas chamam métodos;
  • map, filter, sorted e distinct transformam sequências de forma declarativa;
  • toList(), collect(), count() encerram o processamento;
  • reduce, sum e average agregam valores;
  • anyMatch, allMatch e findFirst verificam condições.