Stream API: Como Transformar Sua Forma de Programar em Java
Introdução
Imagine ter a capacidade de processar coleções de dados de forma elegante, declarativa e eficiente, sem escrever loops intermináveis. A Stream API, introduzida no Java 8, mudou fundamentalmente como programamos. Se você ainda está usando for loops aninhados, está deixando ouro na mesa.
Neste artigo, você descobrirá como Streams reduzem seu código pela metade, melhoram a legibilidade e abrem portas para programação funcional em Java.
👉 Codigo: execute o * Mini-Aplicativo: Análise de Vendas com Stream API que:
* Demonstra: filter, map, reduce, collect, groupingBy, sorting
* Conceitos: Streams, Lambda, Optional, Collectors
Acessar => Código Analise de Vendas
1. O Que é uma Stream?
Uma Stream é uma sequência de elementos que você pode processar de forma funcional e declarativa. Pense nela como um pipeline de transformação de dados.
Características Principais
- 🔄 Não é uma coleção — é uma forma de processar dados
- ⚡ Lazy evaluation — calcula apenas o necessário
- 🔗 Encadeável — você escreve operações em cadeia
- 🎯 Declarativa — você diz o QUÊ fazer, não o COMO
Analogia Prática
Sem Stream (Imperativo):
for (int i = 0; i < lista.size(); i++) {
if (lista.get(i) > 10) {
soma += lista.get(i);
}
}
Com Stream (Declarativo):
int soma = lista.stream()
.filter(x -> x > 10)
.mapToInt(Integer::intValue)
.sum();
2. Operações Intermediárias: Transformando Dados
Operações intermediárias transformam uma Stream em outra Stream. Elas são lazy — só executam quando necessário.
.map() — Transformar Elementos
java
List<String> nomes = Arrays.asList("João", "Maria", "Carlos");
// Converter para maiúsculas
List<String> maiusculas = nomes.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Resultado: [JOÃO, MARIA, CARLOS]
// Extrair comprimento de cada nome
List<Integer> comprimentos = nomes.stream()
.map(String::length)
.collect(Collectors.toList());
// Resultado: [4, 5, 6]
.filter() — Selecionar Elementos
java
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Apenas pares
List<Integer> pares = numeros.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// Resultado: [2, 4, 6, 8, 10]
// Números maiores que 5
List<Integer> maiores = numeros.stream()
.filter(n -> n > 5)
.collect(Collectors.toList());
// Resultado: [6, 7, 8, 9, 10]
.sorted() — Ordenar Elementos
java
List<Integer> numeros = Arrays.asList(5, 2, 8, 1, 9);
// Ordem crescente
List<Integer> crescente = numeros.stream()
.sorted()
.collect(Collectors.toList());
// Resultado: [1, 2, 5, 8, 9]
// Ordem decrescente
List<Integer> decrescente = numeros.stream()
.sorted(Collections.reverseOrder())
.collect(Collectors.toList());
// Resultado: [9, 8, 5, 2, 1]
.distinct() — Remover Duplicatas
java
List<Integer> numeros = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
List<Integer> unicos = numeros.stream()
.distinct()
.collect(Collectors.toList());
// Resultado: [1, 2, 3, 4]
.limit() e .skip()` — Paginar Dados
java
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Primeiros 5 elementos
List<Integer> primeiros = numeros.stream()
.limit(5)
.collect(Collectors.toList());
// Resultado: [1, 2, 3, 4, 5]
// Pular os 3 primeiros, pegar 4
List<Integer> pagina = numeros.stream()
.skip(3)
.limit(4)
.collect(Collectors.toList());
// Resultado: [4, 5, 6, 7]
3. Operações Terminais: Obter Resultados
Operações terminais finalizam a Stream e retornam um resultado. Uma vez chamadas, a Stream é consumida.
.collect() — Coletar em Uma Coleção
java
List<String> nomes = Arrays.asList("Ana", "Bruno", "Carlos");
// Converter para lista
List<String> lista = nomes.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Converter para Set (sem duplicatas)
Set<String> conjunto = nomes.stream()
.collect(Collectors.toSet());
// Converter para String separada por vírgula
String resultado = nomes.stream()
.collect(Collectors.joining(", "));
// Resultado: "Ana, Bruno, Carlos"
.forEach() — Iterar Sobre Elementos
java
List<String> nomes = Arrays.asList("Ana", "Bruno", "Carlos");
nomes.stream()
.forEach(n -> System.out.println("Nome: " + n));
.reduce() — Agregar Valores
java
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
// Somar todos
int soma = numeros.stream()
.reduce(0, Integer::sum);
// Resultado: 15
// Multiplicar todos
int produto = numeros.stream()
.reduce(1, (a, b) -> a * b);
// Resultado: 120
.count() — Contar Elementos
java
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
long quantidade = numeros.stream()
.filter(n -> n > 2)
.count();
// Resultado: 3
.findFirst() e .findAny()` — Encontrar Elemento
java
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
// Primeiro elemento que é par
Optional<Integer> primeiro = numeros.stream()
.filter(n -> n % 2 == 0)
.findFirst();
// Resultado: Optional[2]
// Qualquer elemento (mais eficiente em paralelo)
Optional<Integer> qualquer = numeros.stream()
.filter(n -> n > 3)
.findAny();
// Resultado: Optional[4] ou Optional[5]
.anyMatch(), .allMatch(), .noneMatch()
java
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);
// Existe algum maior que 3?
boolean temMaiorTres = numeros.stream()
.anyMatch(n -> n > 3);
// Resultado: true
// Todos são maiores que 0?
boolean todosMaioresZero = numeros.stream()
.allMatch(n -> n > 0);
// Resultado: true
// Nenhum é negativo?
boolean nenhumNegativo = numeros.stream()
.noneMatch(n -> n < 0);
// Resultado: true
4. Exemplo Prático Completo
Cenário: Análise de Vendas
java
class Venda {
String produto;
double valor;
int quantidade;
public Venda(String produto, double valor, int quantidade) {
this.produto = produto;
this.valor = valor;
this.quantidade = quantidade;
}
}
public class AnalisadorVendas {
public static void main(String[] args) {
List<Venda> vendas = Arrays.asList(
new Venda("Notebook", 3000, 2),
new Venda("Mouse", 50, 10),
new Venda("Teclado", 150, 5),
new Venda("Monitor", 800, 1)
);
// 1. Total de vendas
double total = vendas.stream()
.mapToDouble(v -> v.valor * v.quantidade)
.sum();
System.out.println("Total de vendas: R$ " + total);
// 2. Produtos com valor maior que 100
List<String> caros = vendas.stream()
.filter(v -> v.valor > 100)
.map(v -> v.produto)
.collect(Collectors.toList());
System.out.println("Produtos caros: " + caros);
// 3. Produto mais vendido
String maisVendido = vendas.stream()
.max((v1, v2) -> Integer.compare(v1.quantidade, v2.quantidade))
.map(v -> v.produto)
.orElse("N/A");
System.out.println("Mais vendido: " + maisVendido);
// 4. Agrupar por valor
Map<Boolean, List<Venda>> carosBaratos = vendas.stream()
.collect(Collectors.partitioningBy(v -> v.valor > 500));
System.out.println("Produtos > 500: " + carosBaratos.get(true).size());
}
}
5. Streams Paralelas: Processamento Acelerado
Para grandes volumes de dados, use .parallelStream():
java
List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Sequencial
long somaSeq = numeros.stream()
.map(n -> n * 2)
.count();
// Paralelo (mais rápido em listas grandes)
long somaParalelo = numeros.parallelStream()
.map(n -> n * 2)
.count();
Quando usar paralelo: Listas com +1 milhão de elementos.
6. Boas Práticas com Streams
✅ Encadear operações legíveis
java
// ✅ Bom: Fácil de ler
List<String> resultado = pessoas.stream()
.filter(p -> p.idade > 18)
.map(p -> p.nome)
.sorted()
.collect(Collectors.toList());
// ❌ Ruim: Difícil de ler
List<String> resultado = pessoas.stream().filter(p -> p.idade > 18).map(p -> p.nome).sorted().collect(Collectors.toList());
✅ Usar method references quando possível
java
// ✅ Bom
lista.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
// ❌ Menos elegante
lista.stream()
.map(s -> s.toUpperCase())
.forEach(s -> System.out.println(s));
✅ Evitar side effects
java
// ❌ Ruim: side effect em map
lista.stream()
.map(n -> {
System.out.println(n); // Side effect!
return n * 2;
})
.collect(Collectors.toList());
// ✅ Bom: usar forEach para side effects
lista.stream()
.forEach(System.out::println);
7. Referências
- Stream API Documentation: docs.oracle.com/javase/8/docs/api/java/util/stream
- Collectors: docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors
Functional Programming in Java: Modern Java by Raoul-Gabriel Urma
Conclusão
Stream API não é apenas uma API — é uma mudança de paradigma na forma de pensar sobre processamento de dados. Com Streams, você escreve código mais conciso, legível e expressivo.
De simples filtros a transformações complexas, Streams oferecem um toolkit poderoso que torna seu Java moderno, eficiente e bonito.
A partir de agora, toda vez que você pensar "preciso fazer um loop", pergunte-se: "Como fazer isso com Streams?"
Chamada para Ação
👉 Codigo: execute o * Mini-Aplicativo: Análise de Vendas com Stream API que:
* Demonstra: filter, map, reduce, collect, groupingBy, sorting
* Conceitos: Streams, Lambda, Optional, Collectors
Acessar => Código Analise de Vendas
!👉 Desafio: Pegue um código seu com loops for aninhados e reimplemente usando Streams. Compare o resultado!
📌 Próximo passo: Explore Collections avançadas e padrões de functional programming em Java.