ConceptDifficulty: Advanced1 min

Collectors: Ending a Stream in a List, a Set or a Map

Decide the shape you want out, and the collector is usually already written.

collect is the terminal operation (the last step, which produces a result and makes the stream run) that builds a collection from a stream, and the Collectors class supplies ready-made recipes, called collectors, for lists, sets, maps and strings.

Three pipelines ending in three shapes:

java
List<String> refs   = invoices.stream().map(Invoice::reference).toList();
Set<String> unique  = invoices.stream().map(Invoice::customer).collect(Collectors.toSet());
Map<String, Long> byCustomer =
        invoices.stream().collect(Collectors.groupingBy(Invoice::customer, Collectors.counting()));

Line 1 needs no collector at all, because toList() is a method on the stream itself. Lines 2 to 4 hand collect a recipe, and the recipe decides both the kind of collection and what happens to duplicates.

The collectors worth knowing by name
CollectorProduces
toList() on the streamAn unmodifiable list (adding to it throws), in encounter order
Collectors.toSet()A set, with duplicates gone and no promised order
Collectors.toMap(key, value)A map, throwing if two elements produce one key
Collectors.groupingBy(key)A map from key to a list of the elements with it
Collectors.joining(", ")One string, with a separator

groupingBy is the one that replaces the most code. A second collector inside it says what to do with each group, so counting, summing and collecting to a set are all the same call with a different second argument.

So the end of a pipeline is a choice about shape rather than about work. Decide what you want out, and the collector is usually already written.