CodeGym /Cursos /JAVA 25 SELF /Introdução ao Stream API: por que precisamos de streams

Introdução ao Stream API: por que precisamos de streams

JAVA 25 SELF
Nível 30 , Lição 0
Disponível

1. O problema da abordagem imperativa

Vamos começar com um problema clássico. Suponha que temos uma lista de usuários:

List<User> users = ...; // já preenchida

Digamos que precisamos obter a lista de nomes de todos os usuários maiores de idade (a partir de 18 anos). Como fazíamos isso antes?

List<String> names = new ArrayList<>();
for (User user : users) {
    if (user.getAge() >= 18) {
        names.add(user.getName());
    }
}

À primeira vista, tudo parece simples, mas repare nos passos “técnicos”: declarar uma nova lista para o resultado, percorrer todos os elementos, verificar a condição, adicionar o resultado.

Se a tarefa se complica — por exemplo, precisamos obter uma lista em ordem alfabética de e‑mails únicos de todos os usuários cujos nomes começam com a letra “A” — então o código cresce rapidamente: filtragem, extração de campos, remoção de duplicatas, ordenação, possivelmente conversões no formato do e‑mail etc. No fim, em vez de uma lógica curta e clara, você acaba com uma montanha de boilerplate que é difícil de ler e manter.

As desvantagens dessa abordagem em poucas palavras: muito código repetitivo, alta probabilidade de cometer erros (esquecer a ordenação ou tratar duplicatas de forma incorreta) e dificuldade em combinar operações. Isso é o chamado estilo imperativo: você descreve ao computador, passo a passo, como executar a tarefa, e não o que você quer obter.

2. Stream API — estilo declarativo

Com o lançamento do Java 8 surgiu o Stream API — uma ferramenta que permite trabalhar com coleções no estilo “o que precisa ser feito”, e não “como exatamente fazer”. Essa abordagem é chamada de declarativa.

Mas afinal, o que é um Stream?
Não é uma coleção separada, mas sim um fluxo de dados: uma sequência de elementos que passam por uma cadeia de operações — filtragem, transformação, ordenação, coleta do resultado etc. O stream não armazena nada; ele apenas “puxa” os dados por um pipeline. As operações podem ser combinadas quase como blocos de LEGO: você monta a cadeia e executa.

Exemplo:

List<String> names = users.stream()                // criamos um stream a partir da lista de usuários
    .filter(u -> u.getAge() >= 18)                 // mantemos apenas quem já tem 18 anos 
    .map(User::getName)                            // transformamos User → String (pegamos o nome)
    .collect(Collectors.toList());                 // coletamos o resultado em uma lista

E pronto — em uma única linha descrevemos toda a tarefa: “pegue os usuários, filtre por idade, extraia os nomes, colete em uma lista”. Não é preciso escrever laços manualmente, criar variáveis temporárias e garantir que nada seja esquecido.

O Stream API funciona como uma linha de montagem: você define as etapas de processamento e os dados passam por elas. É bonito, compacto e muito mais fácil de ler.

3. Vantagens do Stream API

  • Concisão e legibilidade. Compare as duas abordagens — imperativa e declarativa — na mesma tarefa. Fica claro qual delas é mais fácil de ler e manter.
  • Composição de operações fácil. As operações são fáceis de “empilhar” em uma cadeia: filter, map, sorted, collect — tudo em uma sequência coesa.
  • Menos erros. O Stream API elimina a rotina: não é preciso gerenciar coleções intermediárias e laços manualmente — menor chance de esquecer um passo ou cometer erro de indexação.
  • Possibilidade de paralelismo. É fácil escalar o processamento para vários núcleos, apenas chamando parallelStream() em vez de stream(). Detalhes — mais adiante.
  • Estilo de programação moderno. Ideias de programação funcional (composição de funções, ausência de laços explícitos) aumentam a expressividade e são valorizadas no mercado.

Breve histórico do Stream API

O Stream API apareceu no Java 8 (2014) e foi um salto qualitativo para a linguagem. Antes disso, qualquer operação não triviale sobre coleções exigia muito código auxiliar, enquanto outras plataformas já ofereciam abordagens declarativas (map, filter, reduce etc.).

Desde então, o Stream API é o padrão de fato para processamento de coleções em Java. Se você quer escrever código Java moderno — não dá para ficar sem ele.

4. Áreas de aplicação do Stream API

  • Filtragem: selecionar apenas os elementos necessários (por exemplo, todos os usuários maiores de 18 anos).
  • Transformação: extrair um campo ou criar um novo objeto (por exemplo, a lista de nomes de usuários).
  • Agregação: calcular soma, média, quantidade, máximo/mínimo.
  • Ordenação: ordenar os elementos por um critério desejado.
  • Agrupamento: dividir os elementos por categorias.
  • Coleta do resultado: coletar em lista, conjunto, mapa, string etc.

Exemplos de tarefas:

  • Obter a lista de e‑mails de todos os usuários cujo nome começa com “A”.
  • Calcular a idade média dos usuários.
  • Encontrar o primeiro usuário com um determinado e‑mail.
  • Reunir todas as cidades de residência únicas em um único conjunto.

5. Nuances úteis

Como o stream funciona

[User1] --\
[User2] ---|--> [ filter ] --> [ map ] --> [ collect ] --> List<String>
[User3] --/

1. filter — permite apenas os usuários que satisfazem a condição.
2. map — transforma o usuário em, por exemplo, e‑mail.
3. collect — coleta os e‑mails na coleção desejada.

Sintaxe: como criar um stream

  • De uma coleção: list.stream() — stream dos elementos da coleção.
  • De um array: Arrays.stream(array)
  • De valores isolados: Stream.of("a", "b", "c")

6. Erros comuns ao migrar para o Stream API

Erro nº 1: tentar modificar a coleção dentro do stream. O Stream API não é destinado a modificar a coleção de origem (por exemplo, não faça list.remove() dentro de forEach). Para remoções use, por exemplo, removeIf na coleção antes ou depois do processamento.

Erro nº 2: confusão entre coleção e stream. Um stream não é uma coleção! O stream “percorre” os elementos uma vez e depois é fechado. Se precisar processar os dados novamente — crie um novo stream().

Erro nº 3: cadeias complexas demais. Não transforme a expressão em um “monstro” com dezenas de operações. Se a cadeia ficar longa — quebre-a em algumas etapas com variáveis intermediárias para legibilidade e depuração.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION