Capítulo 14: Programação Funcional, Lambdas, Interfaces Funcionais e Streams API
Especialização em Sistemas com Java 21 LTS • Módulo de Recursos Avançados e Concorrência
🗺️ Mapa Conceitual do Tópico
flowchart TD
subgraph FUNCIONAL["Pilares da Programação Funcional"]
A["@FunctionalInterface (Método Abstrato Único)"]
B["Expressões Lambda: (x, y) -> x + y"]
C["Method References: String::toUpperCase"]
A --> B & C
end
subgraph INTERFACES["Interfaces Funcionais Nativas (java.util.function)"]
D["Predicate~T~: test(T) -> boolean"]
E["Function~T, R~: apply(T) -> R"]
F["Consumer~T~: accept(T) -> void"]
G["Supplier~T~: get() -> T"]
end
subgraph STREAMS["Pipeline do Streams API"]
H["Fonte de Dados (List, Set, Files)"]
I["Operações Intermediárias (Lazy: filter, map, flatMap)"]
J["Operações Terminais (Eager: collect, reduce, count)"]
H --> I --> J
end
FUNCIONAL --> INTERFACES
INTERFACES --> STREAMS
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style I fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style J fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🧬 1. Fundamentos da Programação Funcional no Java
Desde o Java 8 e amplamente refinado no Java 21 LTS, a linguagem incorporou capacidades declarativas e funcionais. Uma interface anotada com @FunctionalInterface é aquela que possui exatamente um método abstrato (SAM - Single Abstract Method), servindo de base para expressões lambda.
As Quatro Grandes Interfaces Funcionais (java.util.function):
Predicate<T>: Recebe um valor do tipoTe retorna umbooleanprimitivo (test(T)). Utilizado para filtros.Function<T, R>: Recebe um argumentoTe o transforma em um resultadoR(apply(T)). Utilizado para mapeamentos e projeções.Consumer<T>: Recebe um argumentoTe não retorna nada (void accept(T)). Utilizado para efeitos colaterais (logs, escrita).Supplier<T>: Não recebe parâmetros e produz um valorT(get()). Utilizado para fábricas e inicialização preguiçosa (lazy evaluation).
Method References (::):
Açúcar sintático para lambdas que apenas repassam parâmetros para um método pré-existente:
- Método de Instância de Objeto Específico:
System.out::println - Método Estático:
Math::max - Construtor:
ArrayList::new
🌊 2. A Arquitetura do Streams API
Uma Stream em Java é um wrapper funcional em torno de uma fonte de dados que permite computação declarativa encadeada.
Características Fundamentais:
- Não Armazena Dados: Uma Stream não é uma estrutura de dados; ela processa elementos sob demanda.
- Avaliação Preguiçosa (Lazy Evaluation): As operações intermediárias (
filter,map,sorted,distinct,limit) não processam absolutamente nenhum dado no momento da invocação. O processamento real só é disparado quando uma operação terminal é executada. - Fusão de Laços (Loop Fusion): Em vez de iterar sobre a lista inteira no
filtere depois novamente nomap, a JVM funde as etapas e processa cada elemento através de todo o pipeline sequencialmente em uma única passagem.
🧩 3. Agrupamentos e Agregações com Collectors
A classe utilitária java.util.stream.Collectors permite transformar fluxos de streams em mapas complexos, relatórios sumarizados e agrupamentos multi-nível:
// Agrupamento por Categoria com Cálculo de Faturamento Total
Map<Categoria, BigDecimal> faturamentoPorCategoria = vendas.stream()
.collect(Collectors.groupingBy(
Venda::getCategoria,
Collectors.reducing(BigDecimal.ZERO, Venda::getValor, BigDecimal::add)
));
⚡ 4. Paralelismo com parallelStream() e ForkJoinPool
Ao invocar .parallelStream(), o Java divide automaticamente o conjunto de dados em sub-partições utilizando o framework ForkJoinPool.commonPool(), aproveitando todos os núcleos da CPU.
Quando NÃO usar Streams Paralelas:
- Coleções pequenas (onde a sobrecarga de dividir tarefas e sincronizar threads supera o tempo de execução).
- Operações que dependem de estado compartilhado mutável (provocam race conditions).
- Operações de I/O bloqueante (travam as threads compartilhadas do
commonPool).
📊 5. Matriz Comparativa: Laço Clássico (for) vs Streams API
| Critério | Laço Imperativo (for) |
Streams API Declarativa |
|---|---|---|
| Estilo de Programação | Imperativo (foco no “como iterar”) | Declarativo (foco no “o que transformar”) |
| Mutabilidade | Manipula variáveis externas mutáveis | Promove imutabilidade e funções puras |
| Legibilidade em Pipelines | Sofre de aninhamento excessivo (nested loops) | Fluente, linear e de fácil manutenção |
| Execução Paralela | Exige controle manual de Threads/Locks | Trivial com .parallelStream() |
| Overhead de Alocação | Zero (opera sobre ponteiros locais) | Mínimo (criação de objetos de pipeline) |
💻 6. Código de Demonstração Corporativo (Java 21 LTS)
package com.empresa.funcional;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
* Pipeline corporativo de Business Intelligence (BI) de vendas com Streams API.
*/
public class AnalisadorVendasBi {
public enum CanalVenda { ECOMMERCE, LOJA_FISICA, CORPORATIVO_B2B }
public record Venda(String id, String vendedor, CanalVenda canal, BigDecimal total, boolean aprovada) {}
public static void main(String[] args) {
System.out.println("================================================================================");
System.out.println(" PIPELINE DE BI DE VENDAS COM JAVA 21 STREAMS API ");
System.out.println("================================================================================");
List<Venda> historicoVendas = List.of(
new Venda("V01", "Carla Dias", CanalVenda.ECOMMERCE, new BigDecimal("1250.00"), true),
new Venda("V02", "Renato Abreu", CanalVenda.LOJA_FISICA, new BigDecimal("450.00"), false), // Não aprovada
new Venda("V03", "Carla Dias", CanalVenda.ECOMMERCE, new BigDecimal("3200.00"), true),
new Venda("V04", "Felipe Melo", CanalVenda.CORPORATIVO_B2B, new BigDecimal("45000.00"), true),
new Venda("V05", "Renato Abreu", CanalVenda.LOJA_FISICA, new BigDecimal("1800.00"), true),
new Venda("V06", "Felipe Melo", CanalVenda.CORPORATIVO_B2B, new BigDecimal("12000.00"), true)
);
// 1. Filtragem de vendas aprovadas e agrupamento por Canal com totalizador
Map<CanalVenda, BigDecimal> faturamentoPorCanal = historicoVendas.stream()
.filter(Venda::aprovada) // Predicate
.collect(Collectors.groupingBy(
Venda::canal,
Collectors.reducing(BigDecimal.ZERO, Venda::total, BigDecimal::add)
));
System.out.println("📊 FATURAMENTO CONSOLIDADO POR CANAL:");
faturamentoPorCanal.forEach((canal, total) ->
System.out.printf(" Canal: %-18s | Faturamento: R$ %,10.2f%n", canal, total));
// 2. Ranking de Vendedores (Total Vendido e Quantidade de Vendas)
System.out.println("\n🏆 RANKING DE PERFORMANCE DOS VENDEDORES:");
Map<String, List<Venda>> vendasPorVendedor = historicoVendas.stream()
.filter(Venda::aprovada)
.collect(Collectors.groupingBy(Venda::vendedor));
vendasPorVendedor.entrySet().stream()
.map(e -> {
String vendedor = e.getKey();
BigDecimal totalVendedor = e.getValue().stream().map(Venda::total).reduce(BigDecimal.ZERO, BigDecimal::add);
int qtdVendas = e.getValue().size();
return Map.entry(vendedor, Map.entry(totalVendedor, qtdVendas));
})
.sorted((e1, e2) -> e2.getValue().getKey().compareTo(e1.getValue().getKey())) // Ordem Decrescente
.forEach(entry -> {
String vendedor = entry.getKey();
BigDecimal total = entry.getValue().getKey();
int qtd = entry.getValue().getValue();
System.out.printf(" Vendedor: %-15s | Volume: R$ %,10.2f | Pedidos: %d%n", vendedor, total, qtd);
});
System.out.println("================================================================================");
}
}