Transformação de Streams em Coleções com o Método collect em Java
As operações da Stream API processam dados de forma eficiente e preguiçosa, mas o resultado final de um pipeline geralmente precisa ser armazenado em uma estrutura de dados concreta. O método terminal collect
é a "ponte" que permite transformar os elementos de um stream de volta em uma coleção, como uma List
, Set
ou Map
.
A forma mais comum de usar collect
é em conjunto com a classe de utilitários java.util.stream.Collectors
.
Coletando em Listas e Conjuntos (List
e Set
)
Os coletores mais básicos são toList()
e toSet()
.
Collectors.toList()
: Acumula os elementos do stream em umaList
. Não há garantia sobre qual implementação específica deList
será retornada (pode ser umArrayList
ou outra).Collectors.toSet()
: Acumula os elementos em umSet
, o que remove automaticamente elementos duplicados.
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class CollectExample {
public static void main(String[] args) {
List<String> phones = List.of("iPhone 15", "Pixel 8", "Galaxy S24",
"Xiaomi 14", "Pixel 8", "iPhone 15");
List<String> phonesList = phones.stream().collect(Collectors.toList());
System.out.println("Lista resultante: " + phonesList);
Set<String> phonesSet = phones.stream().collect(Collectors.toSet());
System.out.println("Conjunto resultante: " + phonesSet);
}
}
Saída:
Lista resultante: [iPhone 15, Pixel 8, Galaxy S24, Xiaomi 14, Pixel 8, iPhone 15] Conjunto resultante: [Pixel 8, iPhone 15, Galaxy S24, Xiaomi 14]
Coletando em Mapas (Map
)
O coletor Collectors.toMap()
é usado para transformar os elementos do stream em um Map
. É necessário fornecer duas funções: uma para gerar a chave e outra para gerar o valor de cada elemento.
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
record Phone(String name, int price) {}
public class CollectToMapExample {
public static void main(String[] args) {
Stream<Phone> phoneStream = Stream.of(
new Phone("iPhone 15", 999),
new Phone("Pixel 8", 799),
new Phone("Samsung Galaxy S24", 899)
);
Map<String, Integer> phonesMap = phoneStream.collect(
Collectors.toMap(Phone::name, Phone::price)
);
phonesMap.forEach((key, value) -> System.out.println(key + ": " + value + " USD"));
}
}
Lidando com Chaves Duplicadas no toMap
Um problema comum ocorre se o stream contiver elementos que geram a mesma chave. A versão padrão de Collectors.toMap()
lançará uma IllegalStateException
. Para evitar isso, use a versão sobrecarregada que aceita uma terceira função, chamada de "função de mesclagem" (merge function
), que define como resolver a colisão.
Stream<Phone> phoneStreamWithDuplicates = Stream.of(
new Phone("iPhone 15", 999),
new Phone("Pixel 8", 799),
new Phone("iPhone 15", 1099) // Chave duplicada
);
// A função de mesclagem (v1, v2) -> v2 diz: "em caso de colisão, use o valor do segundo elemento".
Map<String, Integer> phonesMap = phoneStreamWithDuplicates.collect(
Collectors.toMap(
Phone::name,
Phone::price,
(existingValue, newValue) -> newValue // Função de mesclagem
)
);
System.out.println(phonesMap); // Saída: {Pixel 8=799, iPhone 15=1099}
Especificando a Coleção de Destino
Se você precisa que o resultado seja uma implementação específica, como um HashSet
ou LinkedList
, use Collectors.toCollection()
. Este método aceita um Supplier
que cria uma nova instância da coleção desejada.
import java.util.HashSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
HashSet<String> phoneSet = Stream.of("iPhone 15", "Pixel 8", "Galaxy S24", "Pixel 8")
.collect(Collectors.toCollection(HashSet::new));
A expressão HashSet::new
é uma referência de método que atua como Supplier
.
A Sobrecarga Manual de collect
Para entender o que acontece "por baixo dos panos", existe uma versão de collect
que aceita três argumentos. Esta forma é a base sobre a qual os coletores da classe Collectors
são construídos.
<R> R collect(Supplier<R> supplier, BiConsumer<R, T> accumulator, BiConsumer<R, R> combiner)
supplier
: Cria o contêiner de resultado (ex:ArrayList::new
).accumulator
: Adiciona um elemento do stream ao contêiner (ex:ArrayList::add
).combiner
: Une dois contêineres. É usada apenas em streams paralelos para combinar os resultados parciais.
import java.util.ArrayList;
import java.util.stream.Stream;
ArrayList<String> phones = Stream.of("iPhone 15", "Pixel 8", "Galaxy S24")
.collect(
ArrayList::new, // supplier
ArrayList::add, // accumulator
ArrayList::addAll // combiner
);
Resumo
- O método
collect
é uma operação terminal que transforma um stream em uma coleção ou outra estrutura de dados. - A classe
Collectors
fornece implementações prontas para as necessidades mais comuns (toList
,toSet
,toMap
). - Ao usar
toMap
, esteja preparado para lidar com chaves duplicadas usando a função de mesclagem. - Para obter uma implementação específica de coleção (como
HashSet
), useCollectors.toCollection(Supplier)
. - A sobrecarga de
collect
com três argumentos (supplier
,accumulator
,combiner
) oferece controle total sobre o processo de coleta.