DEV Community

Streams em Java

O que são Streams?

Streams em Java são uma abstração para processar coleções de dados de forma declarativa. Elas permitem manipular conjuntos de dados de maneira eficiente e funcional, sem a necessidade de laços explícitos.

Diferença entre Collections e Streams

  • Collections armazenam elementos e podem ser modificadas.

  • Streams são fluxos de dados que permitem operações encadeadas, sem modificar a coleção original.

  • Streams são consumíveis: após processar um fluxo, ele não pode ser reutilizado.

Benefícios das Streams

  • Código mais conciso: evita laços e estruturas verbosas.

  • Imutabilidade: não altera os dados originais.

  • Paralelismo: pode ser processado em paralelo para melhor desempenho.

Operações em Streams

As operações (parte do fluxo de processamento) em Streams podem ser classificadas em três tipos principais:

  • Operações Iniciais (Criação da Stream): São as operações que criam uma Stream a partir de uma fonte de dados, como listas, arrays ou valores individuais.

  • Operações Intermediárias: São operações que transformam a Stream em outra Stream sem consumi-la. Essas operações são lazy, ou seja, só são executadas quando uma operação final é chamada.

  • Operações Finais: São operações que processam os elementos da Stream e encerram seu uso. Após uma operação final, a Stream não pode ser reutilizada.

obs.: As operações iniciais (criação da Stream) são obrigatórias, pois uma Stream precisa ser criada antes de ser usada. As operações intermediárias são opcionais. Você pode criar uma Stream e executar uma operação final diretamente, sem transformação intermediária. As operações finais são obrigatórias se você quiser obter um resultado, pois elas disparam o processamento da Stream.

Exemplos:

  1. Apenas criação e operação final (sem intermediárias)
    List<String> nomes = List.of("Ana", "Bruno", "Carlos");
    nomes.stream().forEach(System.out::println);

  2. Com operação intermediária e final
    List<String> nomes = List.of("Ana", "Bruno", "Carlos");
    nomes.stream()
    .filter(nome -> nome.startsWith("B")) // Operação intermediária
    .forEach(System.out::println); // Operação final

  3. Apenas criação e operação intermediária (inválido!)
    List<String> nomes = List.of("Ana", "Bruno", "Carlos");
    Stream<String> filtrada = nomes.stream().filter(nome -> nome.startsWith("B"));

    Isso não gera saída nem efeito visível, porque a Stream é lazy (preguiçosa) e só executa as operações intermediárias quando uma operação final for chamada.

Podemos resumir que:
Criação da Stream → Sempre necessária
Operações intermediárias → Opcionais
Operação final → Necessária para disparar o processamento

Criando Streams

A partir de listas:
List<String> nomes = List.of("Ana", "Bruno", "Carlos");
Stream<String> streamNomes = nomes.stream();
streamNomes.forEach(System.out::println);

A partir de arrays:
String[] array = {"A", "B", "C"};
Stream<String> streamArray = Arrays.stream(array);

A partir de valores:
Stream<String> streamValores = Stream.of("Java", "Python", "C++");

Gerando streams infinitas

  • Stream.iterate(...)

O método iterate gera uma sequência infinita de elementos baseada em uma regra de transformação.

Ele recebe dois argumentos:

1 O valor inicial.
2 Uma função que gera o próximo valor a partir do valor anterior.

Stream<Integer> numeros = Stream.iterate(0, n -> n + 2);
numeros.limit(5).forEach(System.out::println);

Neste caso, iterate começa com 0 e aplica n -> n + 2 para gerar os próximos valores.

  • Stream.generate(...)

O método generate cria uma Stream infinita a partir de um fornecedor (Supplier), ou seja, um método que gera valores sem depender do estado anterior.

Stream<Double> aleatorios = Stream.generate(Math::random);
aleatorios.limit(5).forEach(System.out::println);

Operações Intermediárias

Operações intermediárias retornam uma nova Stream, permitindo encadeamento.

map(): transforma elementos

List<String> nomes = List.of("ana", "bruno");
nomes.stream()
.map(String::toUpperCase)
.forEach(System.out::println);

filter(): filtra elementos

List<String> nomes = List.of("Ana", "Bruno", "Carlos");
nomes.stream()
.filter(nome -> nome.startsWith("B"))
.forEach(System.out::println);

sorted(): ordena elementos

List<Integer> numeros = List.of(3, 1, 4, 2);
numeros.stream()
.sorted()
.forEach(System.out::println);

distinct(): remove duplicatas

List<Integer> numeros = List.of(1, 2, 2, 3, 3, 4);
numeros.stream()
.distinct()
.forEach(System.out::println);

limit() e skip(): limitar e pular elementos

List<Integer> numeros = List.of(1, 2, 3, 4, 5, 6);
numeros.stream()
.skip(2) // Pula os dois primeiros elementos
.limit(3) // Pega apenas três elementos
.forEach(System.out::println);

Operações Finais

As operações finais processam os dados e encerram a Stream.

forEach(): percorre os elementos

List<String> nomes = List.of("Ana", "Bruno", "Carlos");
nomes.stream().forEach(System.out::println);

count(): conta elementos

long quantidade = List.of("A", "B", "C").stream().count();
System.out.println(quantidade);

collect(): coleta elementos em uma coleção

List<String> listaFiltrada = nomes.stream()
.filter(nome -> nome.startsWith("B"))
.collect(Collectors.toList());

reduce(): reduz elementos a um único valor

List<Integer> numeros = List.of(1, 2, 3, 4, 5);
int soma = numeros.stream()
.reduce(0, Integer::sum);
System.out.println("Soma: " + soma);

Top comments (0)