Java 25: Collectors — groupingBy, partitioningBy, joining
easy⏱ 5 mincoursejava
Common terminal collectors
toList(): the default. toSet(): dedupes. toMap(kFn, vFn): throws on duplicate key unless you pass a merge function. joining(sep): for String streams. counting(): nested counter.
groupingBy composability
groupingBy(Order::status) → Map<Status, List<Order>>. Add a downstream collector: groupingBy(Order::status, counting()) → Map<Status, Long>. Or groupingBy(Order::status, summingDouble(Order::total)) → Map<Status, Double>. Or multi-level: groupingBy(Order::status, groupingBy(Order::customer)).
Map<Status, Double> totalByStatus = orders.stream()
.collect(Collectors.groupingBy(
Order::status,
Collectors.summingDouble(Order::total)));
partitioningBy — binary group
partitioningBy(p) returns a Map<Boolean, List<T>> — always has true and false keys, even if empty. Specialized groupingBy for yes/no — 2x faster because it can use a flat array-backed map.
Analyze orders
Given 50 orders with { id, status, total }, compute in one pipeline: (1) total revenue per status, (2) count per status, (3) join order ids per status as a comma-separated string. Log each map.
Collectors.toMap duplicate key
toMap(Order::customer, o -> o) throws IllegalStateException on duplicate customer. Pass a merge: toMap(Order::customer, o -> o, (a, b) -> a.total() > b.total() ? a : b) — keeps the bigger order per customer.
Quiz: Why downstream collectors
Why is groupingBy(k, counting()) better than groupingBy(k).entrySet().stream().map(e -> e.getValue().size())? Single pass + single allocation. No intermediate List per group — the counter is built directly.