Skip to content

diegooilv/stream-api-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 

Repository files navigation

Adeus, for Loops! Como a Stream API do Java Deixa seu Código Mais Limpo e Poderoso

Tags: Java


Introdução

Você já se viu escrevendo blocos de código com loops for aninhados para filtrar e transformar uma simples lista em Java? Neste artigo, vamos explorar como a Stream API pode revolucionar seu código, tornando-o mais declarativo, legível e eficiente, transformando tarefas complexas em operações elegantes de uma única linha.

O Paradigma por Trás da Stream API em Java: O que são Streams?

O que é um stream? Stream é uma sequência de elementos que suporta operações de agregação.

O que é operação de agregação? Agrupar informações e transformá-las em uma só.

Stream não armazena dados! Stream, em uma tradução literal como fluxo, é um processo de dados em sequência, onde cada etapa transforma, reduz ou filtra os elementos de uma fonte (como Arrays, Collections, etc.).

Possui funções intermediárias e terminais, mas o que é isso exatamente?

  • As funções intermediárias são aquelas que transformam, filtram ou organizam os dados, sem encerrar o fluxo. Elas retornam um novo stream, permitindo o encadeamento de várias operações.
  • As funções terminais são aquelas que finalizam o fluxo e produzem um resultado concreto, encerrando o processo.

Explorando o Poder da Stream API de Java: Operações Essenciais

Funções Intermediárias

filter()

Serve para filtrar elementos com base em uma condição. Somente os elementos que satisfazem a expressão booleana passada permanecem no stream.

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> pares = numeros.stream()
    .filter(n -> n % 2 == 0) // Deixa apenas números pares
    .toList();
System.out.println(pares); // [2, 4, 6]

map()

É usada para transformar cada elemento do stream em outro tipo de valor ou formato.

List<String> nomes = Arrays.asList("diego", "ana", "paulo");
List<String> nomesMaiusculos = nomes.stream()
    .map(String::toUpperCase) // Transforma cada nome em maiúsculo
    .toList();
System.out.println(nomesMaiusculos); // [DIEGO, ANA, PAULO]

Outras Funções Intermediárias Importantes

  • distinct(): Remove elementos duplicados.
  • sorted(): Ordena os elementos (alfabeticamente ou numericamente).
  • limit(n): Pega apenas os primeiros n elementos.
  • skip(n): Ignora os primeiros n elementos.
  • peek(): Usada para “espiar” o conteúdo durante o processamento, geralmente para depuração.
List<String> lista = Arrays.asList("Diego", "Ana", "Ana", "Paulo", "Lucas", "Luiza");

List<String> resultado = lista.stream()
    .filter(nome -> nome.length() > 3) // Deixa apenas nomes com mais de 3 letras
    .distinct()                      // Remove duplicados
    .sorted()                        // Ordena alfabeticamente
    .peek(System.out::println)       // Mostra cada nome durante o fluxo
    .limit(4)                        // Limita aos 4 primeiros resultados
    .toList();

System.out.println("Resultado final: " + resultado);

Funções Terminais

collect()

Coleta os elementos do stream e os transforma em uma coleção (como List, Set) ou outro tipo de resultado.

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> pares = numeros.stream()
    .filter(n -> n % 2 == 0)   // mantém apenas pares
    .collect(Collectors.toList()); // coleta em uma lista
System.out.println(pares); // [2, 4, 6]

forEach()

Executa uma ação para cada elemento do stream. Muito útil para imprimir ou aplicar alguma operação.

List<String> nomes = Arrays.asList("Diego", "Ana", "Paulo");
nomes.stream()
    .forEach(System.out::println);
// Saída:
// Diego
// Ana
// Paulo

count()

Retorna a quantidade de elementos que passaram pelo stream, útil para estatísticas.

long quantidadePares = numeros.stream()
    .filter(n -> n % 2 == 0)
    .count();
System.out.println("Quantidade de pares: " + quantidadePares); // 3

reduce()

Reduz todos os elementos do stream a um único valor, aplicando uma operação cumulativa, como soma, multiplicação ou concatenação.

int soma = numeros.stream()
    .reduce(0, (a, b) -> a + b); // soma todos os números
System.out.println("Soma total: " + soma); // 21

anyMatch(), allMatch(), noneMatch()

Verificam se algum, todos ou nenhum elemento atende a uma condição.

boolean temPar = numeros.stream().anyMatch(n -> n % 2 == 0);
System.out.println("Tem algum número par? " + temPar); // true

findFirst() / findAny()

Retornam o primeiro ou qualquer elemento do stream que corresponda a um critério. Retornam um Optional.

Optional<Integer> primeiroPar = numeros.stream()
    .filter(n -> n % 2 == 0)
    .findFirst();
System.out.println("Primeiro par: " + primeiroPar.orElse(-1)); // 2

Boas Práticas e Erros Comuns na Java Stream API

  • Evitar reusar a mesma stream: Streams em Java são consumidos por operações terminais. Tentar reutilizar o mesmo stream gera IllegalStateException, pois o fluxo de dados já foi “fechado”.

    Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    • Preferir operações intermediárias stateless quando possível: Operações stateless não mantêm estado entre os elementos do fluxo.
    List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5, 6);
    numeros.stream()
           .filter(n -> n % 2 == 0) // stateless
           .map(n -> n * 2)       // stateless
           .forEach(System.out::println);

    Em streams grandes, operações stateful podem ser lentas ou causar uso excessivo de memória. Operações stateless permitem paralelização mais eficiente.

Conclusão

Como vimos, a Stream API do Java é mais do que uma ferramenta: é uma mudança de paradigma que promove um código mais limpo e funcional. Ao dominar operações como map e filter, você escreve menos e expressa mais.

Participe

Agora é sua vez! Adicione seus conhecimentos ao repositório do projeto: github.com/diegooilv/stream-api-java! Veja a pasta exemplos/ e adicione um exemplo seu!

Referências