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 deListserá retornada (pode ser umArrayListou 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
Collectorsfornece 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
collectcom três argumentos (supplier,accumulator,combiner) oferece controle total sobre o processo de coleta.