Streams API em Java

Introdução

Com a chegada do Java 8, a linguagem evoluiu significativamente, introduzindo conceitos da programação funcional. Entre as novidades mais marcantes está a Streams API, uma poderosa ferramenta para manipular coleções de forma declarativa, concisa e eficiente.

A Streams API permite processar dados em sequência (como listas, arrays ou coleções complexas) aplicando operações como mapeamento, filtragem, redução e combinação, tudo isso de maneira fluente e expressiva, eliminando boa parte do código imperativo tradicional com loops e condicionais.

Neste artigo, vamos explorar passo a passo desde as bases (List e Iterator) até as operações mais avançadas de Map, Filter, Reduce, Match, Min e Max, com exemplos práticos em Java.

1 – List

A interface “List” representa uma coleção ordenada de elementos que pode conter valores duplicados. É uma das coleções mais usadas em Java.

Exemplo:

import java.util.*;

public class ExemploList {
    public static void main(String[] args) {
        List<String> nomes = new ArrayList<>();
        nomes.add("Paulo");
        nomes.add("Maria");
        nomes.add("Ana");
        
        System.out.println(nomes);
    }
}

Saída:

[Paulo, Maria, Ana]

2 – Iterator

Antes das Streams, o “Iterator” era a principal forma de percorrer coleções.

Exemplo:

import java.util.*;

public class ExemploIterator {
    public static void main(String[] args) {
        List<String> nomes = Arrays.asList("Paulo", "Maria", "Ana");
        Iterator<String> iterator = nomes.iterator();
        
        while (iterator.hasNext()) {
            String nome = iterator.next();
            System.out.println(nome.toUpperCase());
        }
    }
}

Apesar de funcional (no sentido de funcionar para o fim destinado), esse estilo é verboso e pouco expressivo, é aqui que a Streams API entra em cena.

3 – Stream

A interface “Stream<T>” permite processar coleções em pipeline, ou seja, uma sequência de operações encadeadas que transformam os dados de forma declarativa.

Exemplo:

import java.util.*;

public class ExemploStream {
    public static void main(String[] args) {
        List<String> nomes = Arrays.asList("Paulo", "Maria", "Ana");

        nomes.stream()
             .map(String::toUpperCase)
             .forEach(System.out::println);
    }
}

Saida:

PAULO
MARIA
ANA

4 – Map

A operação “map()” transforma cada elemento do stream em outro valor (sem alterar o original).

Exemplo:

import java.util.*;

public class ExemploMap {
    public static void main(String[] args) {
        List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);

        numeros.stream()
               .map(n -> n * 2)
               .forEach(System.out::println);
    }
}

Saída:

2
4
6
8
10

Aqui multiplicamos cada elemento por 2, sem modificar a lista original, o “map()” é excelente para transformações.

5 – Filter

A operação “filter()” permite filtrar elementos com base em uma condição booleana.

Exemplo:

import java.util.*;

public class ExemploFilter {
    public static void main(String[] args) {
        List<Integer> numeros = Arrays.asList(3, 6, 9, 12, 15, 18);

        numeros.stream()
               .filter(n -> n > 10)
               .forEach(System.out::println);
    }
}

Saída:

12
15
18

Aqui, apenas números maiores que 10 são exibidos.

6 – Reduce

A operação “reduce()” combina todos os elementos de um stream em um único valor, seja uma soma, multiplicação, concatenação etc.

Exemplo:

import java.util.*;

public class ExemploReduce {
    public static void main(String[] args) {
        List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);

        int soma = numeros.stream()
                          .reduce(0, (a, b) -> a + b);

        System.out.println("Soma: " + soma);
    }
}

Saída:

Soma: 15

O “reduce()” é muito usado em operações de agregação e estatísticas.

7 – Match

As operações anyMatch, allMatch e noneMatch permitem verificar se alguma, todas ou nenhuma condição é satisfeita pelos elementos do stream.

Exemplo:

import java.util.*;

public class ExemploMatch {
    public static void main(String[] args) {
        List<Integer> numeros = Arrays.asList(5, 10, 15, 20);

        boolean algumMaiorQue15 = numeros.stream().anyMatch(n -> n > 15);
        boolean todosPares = numeros.stream().allMatch(n -> n % 2 == 0);
        boolean nenhumNegativo = numeros.stream().noneMatch(n -> n < 0);

        System.out.println("Algum maior que 15? " + algumMaiorQue15);
        System.out.println("Todos pares? " + todosPares);
        System.out.println("Nenhum negativo? " + nenhumNegativo);
    }
}

Saída:

Algum maior que 15? true
Todos pares? false
Nenhum negativo? true

8 – Min e Max

As operações min() e max() encontram, respectivamente, o menor e o maior valor de um stream.

Exemplo:

import java.util.*;

public class ExemploMinMax {
    public static void main(String[] args) {
        List<Integer> numeros = Arrays.asList(7, 2, 9, 4, 6);

        int menor = numeros.stream().min(Integer::compareTo).get();
        int maior = numeros.stream().max(Integer::compareTo).get();

        System.out.println("Menor: " + menor);
        System.out.println("Maior: " + maior);
    }
}

Saída:

Menor: 2
Maior: 9

Conclusão

A Streams API revolucionou a forma de trabalhar com coleções no Java, permitindo que desenvolvedores escrevam códigos mais limpos, legíveis e eficientes.


Com operações como “map”, “filter” e “reduce”, é possível substituir loops complexos por pipelines funcionais elegantes, que expressam o “o que” deve ser feito, e não “como” fazer.

Além de tornar o código mais enxuto, a Streams API é otimizada para execução paralela, o que melhora a performance em grandes volumes de dados quando usada com parallelStream().

Dominar Streams API é um passo essencial para quem deseja escrever Java moderno e profissional.