
Programação Funcional em Java
1. O que é Programação Funcional?
Seção intitulada “1. O que é Programação Funcional?”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.
2. Lambdas
Seção intitulada “2. Lambdas”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á!"); // lambdaA 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) -> { ... }
3. forEach
Seção intitulada “3. forEach”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 forEachfor (String nome : nomes) { System.out.println(nome);}
// Com forEach (lambda)nomes.forEach(nome -> System.out.println(nome));3.1 Method references
Seção intitulada “3.1 Method references”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.
4. Streams
Seção intitulada “4. Streams”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]4.1 map — transformando elementos
Seção intitulada “4.1 map — transformando elementos”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]4.2 filter — selecionando elementos
Seção intitulada “4.2 filter — selecionando elementos”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]4.3 sorted e distinct
Seção intitulada “4.3 sorted e distinct”sorted() ordena os elementos (usando a ordem natural) e distinct() elimina duplicatas:
numeros.stream().sorted().distinct().toList(); // [1, 3, 5, 8]4.4 Ordenação com lambda
Seção intitulada “4.4 Ordenação com lambda”Para ordenar objetos sem ordem natural, usamos um Comparator escrito como lambda:
// Ordena por nota, da maior para a menoralunos.sort((a1, a2) -> Double.compare(a2.getNota(), a1.getNota()));
// O mesmo comportamento com Comparatoralunos.sort(Comparator.comparing(Aluno::getNota).reversed());5. Operações terminais
Seção intitulada “5. Operações terminais”As operações terminais encerram o pipeline e produzem o resultado:
long total = numeros.stream().count(); // conta elementosnumeros.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.
6. reduce e agregações
Seção intitulada “6. reduce e agregações”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); // 15Para valores numéricos, existem atalhos prontos:
int total = numeros.stream().mapToInt(n -> n).sum();double media = numeros.stream().mapToInt(n -> n).average().orElse(0);7. Verificando condições
Seção intitulada “7. Verificando condições”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 umOptional.
8. Exemplo completo
Seção intitulada “8. Exemplo completo”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éticaList<String> aprovados = alunos.stream() .filter(aluno -> aluno.getNota() >= 6) .map(Aluno::getNome) .sorted() .toList();// [Ana, Carla]9. Resumo
Seção intitulada “9. Resumo”Neste tópico você viu que a programação funcional no Java é feita com lambdas e a API de Streams:
forEachpercorre coleções e streams;- Method references (
Classe::método) encurtam lambdas que apenas chamam métodos; map,filter,sortededistincttransformam sequências de forma declarativa;toList(),collect(),count()encerram o processamento;reduce,sumeaverageagregam valores;anyMatch,allMatchefindFirstverificam condições.