Java 47: Stream Collectors Deep Dive — toMap, flatMap & teeing
easy⏱ 5 mincoursejava
toMap & the merge function
Collectors.toMap(keyFn, valueFn) throws IllegalStateException on duplicate keys — a frequent surprise. Supply a third merge argument to resolve collisions: toMap(k, v, (a, b) -> a + b) sums duplicates. A fourth argument picks the map type (TreeMap::new for sorted). Always think about collisions before reaching for toMap.
Map<String, Integer> byName = people.stream()
.collect(Collectors.toMap(Person::name, Person::age,
(a, b) -> a, // keep first on collision
TreeMap::new)); // sorted map
flatMap flattens nested streams
flatMap maps each element to a stream and concatenates them — turning Stream<Order> (each with a List<Item>) into a flat Stream<Item>. It's the canonical way to expand one-to-many relationships before grouping or summing. mapMulti (Java 16) is a lower-allocation alternative for the same job.
orders.stream()
.flatMap(o -> o.items().stream()) // Stream<Order> -> Stream<Item>
.mapToInt(Item::price)
.sum();
Downstream collectors & teeing
groupingBy(classifier, downstream) post-processes each group: groupingBy(Item::category, summingInt(Item::price)) gives revenue per category. teeing(c1, c2, merger) (Java 12) runs two collectors over the same stream and merges their results — e.g. compute count and sum in one pass, then divide for an average. It's how you get multiple aggregates without re-streaming.
Flatten, group, and tee
Given orders each holding line items({category, price}): (1) flatMap to a flat item list. (2) groupingBy category, summingInt price -> revenue per category, printed sorted. (3) teeing: compute min price and max price in one pass and print the spread. (4) toMap category->count, supplying a merge that sums on collision. Print all four.
Collectors.groupingBy is not ordered
groupingBy returns a HashMap with no order guarantee. For sorted output use groupingBy(classifier, TreeMap::new, downstream), or stream the entry set and sorted afterwards. Don't assume insertion or natural order from a plain groupingBy result.
Quiz: toMap duplicate keys
What happens when Collectors.toMap(keyFn, valFn) produces two equal keys? It throws IllegalStateException: Duplicate key at collection time. The two-arg form has no merge policy, so duplicates are a hard error. Use groupingBy when keys naturally repeat, or pass a merge function to toMap to define collision behavior.