Métodos da Stream API em Java: Filtragem, Mapeamento e Iteração
A Stream API oferece um conjunto de métodos para processar dados de forma declarativa. Entre as operações mais comuns estão a iteração, a filtragem e a transformação (mapeamento) dos elementos de um fluxo.
Iteração de Elementos: O Método forEach
Para percorrer os elementos de um stream e executar uma ação em cada um deles, é utilizado o método forEach
. Esta é uma operação terminal, o que significa que ela consome o stream e encerra o pipeline de operações. O método forEach
recebe como parâmetro um objeto Consumer
, que define a ação a ser executada para cada elemento.
Por exemplo, para imprimir cada string de um stream no console:
import java.util.stream.Stream;
// ...
Stream<String> citiesStream = Stream.of("Paris", "London", "Madrid", "Berlin", "Brussels");
citiesStream.forEach(s -> System.out.println(s));
É possível simplificar esta chamada utilizando uma referência de método (Method Reference), que é uma forma compacta de uma expressão lambda que apenas chama um método existente.
Stream<String> citiesStream = Stream.of("Paris", "London", "Madrid", "Berlin", "Brussels");
citiesStream.forEach(System.out::println);
Filtragem de Elementos: O Método filter
A filtragem de elementos em um stream é realizada com o método filter
, uma operação intermediária. Ele recebe como parâmetro um Predicate<T>
, que é uma função que retorna true
ou false
para um elemento. O método filter
então retorna um novo stream contendo apenas os elementos para os quais a condição foi avaliada como true
.
Stream<String> citiesStream = Stream.of("Paris", "London", "Madrid", "Berlin", "Brussels");
citiesStream.filter(s -> s.length() == 6)
.forEach(System.out::println);
Aqui, apenas as strings com exatamente 6 caracteres passarão pelo filtro, resultando na seguinte saída:
London Madrid Berlin
Transformação de Elementos: O Método map
O mapeamento é uma operação que converte cada elemento de um stream em outro objeto. Para isso, utiliza-se o método map
, que é uma operação intermediária. A característica chave do map
é que ele realiza uma transformação um-para-um: para cada elemento de entrada, ele produz exatamente um elemento de saída, embora o tipo do elemento possa mudar.
Ele recebe uma Function
que define como transformar um elemento de tipo T
em um elemento de tipo R
, retornando um novo Stream<R>
.
Por exemplo, podemos transformar um Stream<Phone>
em um Stream<String>
contendo apenas os nomes dos telefones.
// Supondo que a classe Phone já foi definida
Stream<Phone> phoneStream = Stream.of(
new Phone("iPhone 16", 54000),
new Phone("Lumia 950", 45000),
new Phone("Samsung Galaxy S 24", 40000)
);
phoneStream.map(p -> p.getName()) // Transforma o fluxo de Phone para um fluxo de String
.forEach(s -> System.out.println(s));
Para otimizar o processo ao transformar objetos em tipos primitivos, a Stream API oferece métodos especializados como mapToInt()
, mapToLong()
e mapToDouble()
.
iPhone 16 Lumia 950 Samsung Galaxy S 24
Mapeamento Plano: O Método flatMap
Enquanto map
realiza uma transformação um-para-um, o flatMap
é projetado para cenários um-para-muitos. Ele é utilizado quando uma operação de transformação sobre um elemento resulta não em um único novo elemento, mas em uma sequência (ou seja, em um novo stream) de elementos.
O flatMap
funciona em duas etapas: primeiro, ele aplica uma função a cada elemento, que deve retornar um stream de novos elementos; segundo, ele "achata" (do inglês flattens) todos os streams gerados em um único fluxo contínuo.
Imagine que, para cada telefone, desejamos gerar duas strings: uma com o preço normal e outra com desconto. O flatMap
é perfeito para isso.
// Supondo que a classe Phone já foi definida
Stream<Phone> phoneStream = Stream.of(
new Phone("iPhone 16", 54000),
new Phone("Lumia 950", 45000)
);
phoneStream.flatMap(p -> Stream.of(
String.format("Modelo: %s, Preço normal: %d", p.getName(), p.getPrice()),
String.format("Modelo: %s, Preço com desconto: %d", p.getName(), (int)(p.getPrice() * 0.9))
))
.forEach(s -> System.out.println(s));
Para cada objeto Phone
, a lambda cria um novo Stream
contendo duas strings. O flatMap
une esses pequenos streams em um único fluxo de saída.
Modelo: iPhone 16, Preço normal: 54000 Modelo: iPhone 16, Preço com desconto: 48600 Modelo: Lumia 950, Preço normal: 45000 Modelo: Lumia 950, Preço com desconto: 40500
Exemplo Completo e Prático
Para consolidar os conceitos, o código abaixo define a classe Phone
e utiliza um pipeline de streams para filtrar e transformar os dados em um método main
executável.
import java.util.stream.Stream;
class Phone {
private String name;
private int price;
public Phone(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
}
public class StreamExample {
public static void main(String[] args) {
Stream.of(
new Phone("iPhone 16", 54000),
new Phone("Lumia 950", 45000),
new Phone("Samsung Galaxy S 24", 40000),
new Phone("LG G4", 32000)
)
.filter(p -> p.getPrice() < 50000) // 1. Filtra telefones com preço abaixo de 50000
.map(p -> p.getName()) // 2. Mapeia para obter apenas o nome
.forEach(System.out::println); // 3. Imprime cada nome no console
}
}
Lumia 950 Samsung Galaxy S 24 LG G4
Resumo
forEach
: É uma operação terminal usada para executar uma ação em cada elemento de um stream.filter
: É uma operação intermediária que retorna um novo stream contendo apenas os elementos que satisfazem uma condição.map
: É uma operação intermediária para transformações um-para-um, convertendo cada elemento em um novo elemento.flatMap
: É uma operação intermediária para transformações um-para-muitos, convertendo cada elemento em um stream de novos elementos e unificando os resultados em um único fluxo.