Unravel โบ Java concepts โบ Collections and streams
Collectors: groupingBy, toMap, joining
A Collector decides what a stream becomes at the end: a list, a map, a group per key, a count, a joined string.
Learn these first
At a glance
Sorting a pile of mail into pigeonholes: the collector says which pigeonhole each letter goes into and what you do with the letters inside (count them, list them, add up the postage).
- 1stream.collect(Collectors.groupingBy(Order::customer)) gives Map<Customer, List<Order>>A second argument decides what to do with each group: counting(), summingInt(Order::qty), mapping(Order::id, toList()).
- 2partitioningBy(x -> x > 10) splits into exactly two groups (true and false)
- 3toMap(keyFn, valueFn) throws IllegalStateException on duplicate keys unless you pass a merge function as a third argument: toMap(k, v, Integer::sum)
- 4joining(", ", "[", "]") builds a string, averagingInt computes means, summarizingInt gives count, sum, min, max and average in one pass, teeing runs two collectors and combines the results
- groupingBy returns a HashMap by default; pass a map factory (TreeMap::new) for sorted keys
- Many aggregations are better in the database (GROUP BY returns less data)
- A collector is made of four functions: supplier (create the container), accumulator (add one element), combiner (merge two partial containers, used by parallel streams) and finisher
import java.util.*;import java.util.stream.*;public class Reports { record Order(String customer, int qty, double total) {} static Map<String, Double> totalPerCustomer(List<Order> orders) { return orders.stream().collect(Collectors.groupingBy(Order::customer, TreeMap::new, Collectors.summingDouble(Order::total))); } static Map<Boolean, Long> bigVsSmall(List<Order> orders) { return orders.stream().collect(Collectors.partitioningBy(o -> o.qty() > 10, Collectors.counting())); } static Map<String, Integer> qty(List<Order> orders) { return orders.stream().collect(Collectors.toMap(Order::customer, Order::qty, Integer::sum)); }}- line 6 uses groupingBy, the core of Collectors: groupingBy, toMap, joining
- line 9 uses partitioningBy, the core of Collectors: groupingBy, toMap, joining
- line 12 uses toMap, the core of Collectors: groupingBy, toMap, joining
Read the full explanation (about 1 min)
A Collector decides what a stream becomes at the end: a list, a map, a group per key, a count, a joined string. groupingBy is the stream version of SQL's GROUP BY.
A mental model
Sorting a pile of mail into pigeonholes: the collector says which pigeonhole each letter goes into and what you do with the letters inside (count them, list them, add up the postage).
How it works
stream.collect(Collectors.groupingBy(Order::customer)) gives Map<Customer, List<Order>>. A second argument decides what to do with each group: counting(), summingInt(Order::qty), mapping(Order::id, toList()).
partitioningBy(x -> x > 10) splits into exactly two groups (true and false).
toMap(keyFn, valueFn) throws IllegalStateException on duplicate keys unless you pass a merge function as a third argument: toMap(k, v, Integer::sum).
joining(", ", "[", "]") builds a string, averagingInt computes means, summarizingInt gives count, sum, min, max and average in one pass, teeing runs two collectors and combines the results.
Going deeper
groupingBy returns a HashMap by default; pass a map factory (TreeMap::new) for sorted keys. groupingBy rejects null keys (NullPointerException).
Many aggregations are better in the database (GROUP BY returns less data). Use stream grouping for data already in memory.
Downstream collectors compose, so "total per customer per month" is a groupingBy inside a groupingBy. Keep nesting to two levels or name the pieces.
Under the hood
A collector is made of four functions: supplier (create the container), accumulator (add one element), combiner (merge two partial containers, used by parallel streams) and finisher. groupingBy builds a map whose values are the downstream collectors' containers.
Common mistakes
toMapwithout a merge function when keys can repeat.- Null keys in
groupingBy. - Treating the resulting map as ordered.
How Unravel recognises it
When you paste code, Unravel looks for these signs of collectors: groupingby, tomap, joining. Spotting them yourself is a good habit when you read unfamiliar code:
- Method calls:
groupingBy()partitioningBy()toMap()counting()summingInt()summingLong()summingDouble()averagingInt()averagingDouble()mapping()collectingAndThen()teeing()toCollection()summarizingInt()and 3 more
Part of a learning path
- Streams in depth ยท previous: Stream operations in depth ยท next: Parallel streams
Related concepts
Frequently asked questions
What is Collectors: groupingBy, toMap, joining?
A Collector decides what a stream becomes at the end: a list, a map, a group per key, a count, a joined string. groupingBy is the stream version of SQL's GROUP BY.
What are the most common mistakes with Collectors: groupingBy, toMap, joining?
toMap without a merge function when keys can repeat. Null keys in groupingBy. Treating the resulting map as ordered.
How can I see Collectors: groupingBy, toMap, joining in real code?
Paste your own Java into Unravel and it will point out where collectors: groupingby, tomap, joining appears, explain how it works at the detail level you choose, and draw the flow of the code around it.