The Collectors.groupingBy is a powerful method in Java’s Stream API that allows grouping of elements in a stream based on a classification function, and it works well with downstream collectors. Here’s how you can use Collectors.groupingBy with downstream collectors effectively.
Syntax of Collectors.groupingBy with a Downstream Collector
The key method signature is:
Collectors.groupingBy(Classifier, Downstream)
- Classifier: A function that determines how the elements are grouped (e.g., based on a key derived from the element).
- Downstream Collector: The collector used to process the grouped elements further (e.g., counting, mapping, reducing, collecting to a list, etc.).
Example 1: Grouping Elements and Counting Them
To group elements based on a key and count the number of elements in each group:
Map<String, Long> result = items.stream()
.collect(Collectors.groupingBy(
item -> item.getCategory(), // Classifier
Collectors.counting() // Downstream collector
));
- This produces a map where the key is the category, and the value is the count of items in that category.
Example 2: Group and Collect as a List
If you want to group the elements and collect them in lists:
Map<String, List<Item>> result = items.stream()
.collect(Collectors.groupingBy(
item -> item.getCategory(), // Classifier
Collectors.toList() // Downstream collector
));
- Groups all elements into lists under their respective categories.
Example 3: Group and Use Summarizing Collector
To produce a statistical summary (e.g., count, sum, min, max, average) for each group:
Map<String, DoubleSummaryStatistics> result = items.stream()
.collect(Collectors.groupingBy(
item -> item.getCategory(), // Classifier
Collectors.summarizingDouble(Item::getPrice) // Summarizing collector
));
- This gives a map where each group has a
DoubleSummaryStatisticsobject that includes the sum, count, min, max, and average for the prices in that group.
Example 4: Group and Reduce Values
To group elements and simultaneously reduce the values for each group:
Map<String, Optional<Item>> result = items.stream()
.collect(Collectors.groupingBy(
item -> item.getCategory(), // Classifier
Collectors.reducing((item1, item2) ->
item1.getPrice() > item2.getPrice() ? item1 : item2) // Downstream: Find max price
));
- This produces a map where each category has an
Optional<Item>representing the item with the highest price.
Example 5: Multi-Level Grouping
You can nest multiple groupingBy collectors to perform hierarchical grouping:
Map<String, Map<String, List<Item>>> result = items.stream()
.collect(Collectors.groupingBy(
Item::getCategory, // First-level group by category
Collectors.groupingBy(Item::getType) // Second-level group by type
));
- This creates a nested map where the first key is the category, and the value contains another map grouped by type.
Practical Example Walkthrough:
If you have a list of strings and want to:
- Group them by their length.
- Collect their counts using
Collectors.counting().
Here’s how:
List<String> names = List.of("apple", "banana", "orange", "kiwi", "pear");
Map<Integer, Long> groupedCounts = names.stream()
.collect(Collectors.groupingBy(
String::length, // Classifier: Group by string length
Collectors.counting() // Downstream collector: Count elements
));
System.out.println(groupedCounts);
// Output: {4=2, 5=2, 6=1}
Key Points of Using Downstream Collectors:
- Flexibility: You can use different collectors (e.g.,
toList,toSet,counting,joining, etc.) to define how grouped elements are processed. - Composition: Downstream collectors can be combined, nested, or customized using
collectingAndThenorreducing. - Extensibility: Custom
Collectorimplementations can be used as downstream collectors for complex use cases.
This approach simplifies processing grouped data and eliminates the need for verbose loops or manual grouping logic.
