Downstream Collectors
filtering, flatMapping, reducing and collectingAndThen: the second tier of Collectors.
Open this lesson in the learning hubKey points
Collectors.filtering(Java 9+) filters inside a group, so a group that empties out still exists.- Filtering before
groupingByinstead makes that group vanish. Which one you want is a real decision. flatMappingflattens per group: orders to line items, teams to members, in a single pass.reducingfolds a group to one value whencountingandsummingIntare not enough.collectingAndThenbolts a finishing step on: freeze the list, unwrap the Optional, format the number.- They nest as deep as you like, but two levels is usually where a reader gives up.
Example
import java.util.*;
import java.util.stream.*;
public class Main {
record Order(String customer, List<String> items, int total) {}
public static void main(String[] args) {
List<Order> orders = List.of(
new Order("ada", List.of("pen", "ink"), 40),
new Order("ada", List.of("pad"), 5),
new Order("bo", List.of("mug"), 8));
// filtering: the group survives even when nothing in it passes.
Map<String, List<Order>> inside = orders.stream().collect(Collectors.groupingBy(
Order::customer, TreeMap::new,
Collectors.filtering(o -> o.total() > 10, Collectors.toList())));
// Filtering before the grouping instead: bo disappears completely.
Map<String, List<Order>> outside = orders.stream().filter(o -> o.total() > 10)
.collect(Collectors.groupingBy(Order::customer, TreeMap::new, Collectors.toList()));
System.out.println("filtering() keys : " + inside.keySet());
System.out.println("filter-first keys: " + outside.keySet());
System.out.println("bo, inside : " + inside.get("bo"));
// flatMapping: every item each customer bought, in one pass.
Map<String, List<String>> items = orders.stream().collect(Collectors.groupingBy(
Order::customer, TreeMap::new,
Collectors.flatMapping(o -> o.items().stream(), Collectors.toList())));
System.out.println("flatMapping : " + items);
// reducing folds a group; collectingAndThen finishes the whole result.
Map<String, Integer> spend = orders.stream().collect(Collectors.groupingBy(
Order::customer, TreeMap::new, Collectors.reducing(0, Order::total, Integer::sum)));
List<String> frozen = orders.stream().collect(Collectors.collectingAndThen(
Collectors.mapping(Order::customer, Collectors.toList()), List::copyOf));
System.out.println("reducing : " + spend);
System.out.println("collectingAndThen: " + frozen);
}
}
Filtering inside a group keeps the key; filtering before it deletes the key.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.