Skip to main content
Advertisement

14.4 Collectors In Depth

The Collectors class provides collection strategies for the collect() terminal operation. Beyond basic toList() and joining(), learn powerful grouping and aggregation features.

1. Grouping with groupingBy

record Person(String name, int age, String city, double salary) {}

List<Person> people = List.of(
new Person("Alice", 28, "Seoul", 5000),
new Person("Bob", 35, "Busan", 6000),
new Person("Charlie", 28, "Seoul", 7000),
new Person("Dave", 35, "Seoul", 4500),
new Person("Eve", 22, "Daegu", 3500)
);

// Group by city
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::city));

// Group by city, extract names only (downstream collector)
Map<String, List<String>> namesByCity = people.stream()
.collect(Collectors.groupingBy(
Person::city,
Collectors.mapping(Person::name, Collectors.toList())
));

// Count per city
Map<String, Long> countByCity = people.stream()
.collect(Collectors.groupingBy(Person::city, Collectors.counting()));

// Average salary per city
Map<String, Double> avgSalaryByCity = people.stream()
.collect(Collectors.groupingBy(Person::city,
Collectors.averagingDouble(Person::salary)));

Multi-level Grouping

Map<String, Map<Integer, List<Person>>> byCityAndAge = people.stream()
.collect(Collectors.groupingBy(Person::city,
Collectors.groupingBy(Person::age)));

2. partitioningBy - Split Into Two Groups

Map<Boolean, List<Person>> partitioned = people.stream()
.collect(Collectors.partitioningBy(p -> p.salary() >= 5000));

System.out.println("High salary: " + partitioned.get(true).stream()
.map(Person::name).collect(Collectors.joining(", ")));
// High salary: Alice, Bob, Charlie

// Prime vs composite numbers
List<Integer> nums = IntStream.rangeClosed(2, 20).boxed().toList();
Map<Boolean, List<Integer>> primes = nums.stream()
.collect(Collectors.partitioningBy(n -> {
for (int i = 2; i * i <= n; i++) if (n % i == 0) return false;
return true;
}));
System.out.println("Primes: " + primes.get(true)); // [2, 3, 5, 7, 11, 13, 17, 19]

3. toMap - Collect to Map

Map<String, Double> nameSalaryMap = people.stream()
.collect(Collectors.toMap(Person::name, Person::salary));

// Handle key collision
Map<String, Double> avgSalaryMap = people.stream()
.collect(Collectors.toMap(
Person::city,
Person::salary,
(existing, replacement) -> (existing + replacement) / 2
));

// Preserve insertion order (LinkedHashMap)
Map<String, Double> orderedMap = people.stream()
.collect(Collectors.toMap(
Person::name, Person::salary,
(a, b) -> a,
LinkedHashMap::new
));

4. Statistics Collectors

DoubleSummaryStatistics stats = people.stream()
.collect(Collectors.summarizingDouble(Person::salary));

System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());

5. Other Key Collectors

// joining
String joined = people.stream()
.map(Person::name)
.collect(Collectors.joining(", ", "[", "]"));
// [Alice, Bob, Charlie, Dave, Eve]

// counting
long count = people.stream().collect(Collectors.counting()); // 5

// summingInt / averagingInt
int total = people.stream()
.collect(Collectors.summingInt(p -> (int) p.salary()));

Pro Tip

Sorted grouping result:

Map<String, Long> sorted = people.stream()
.collect(Collectors.groupingBy(
Person::city,
TreeMap::new, // Keys auto-sorted
Collectors.counting()
));

Collectors.teeing (Java 12+): Process a stream with two collectors simultaneously.

record MinMax(double min, double max) {}

MinMax result = people.stream()
.collect(Collectors.teeing(
Collectors.minBy(Comparator.comparingDouble(Person::salary)),
Collectors.maxBy(Comparator.comparingDouble(Person::salary)),
(min, max) -> new MinMax(
min.map(Person::salary).orElse(0.0),
max.map(Person::salary).orElse(0.0)
)
));
System.out.println(result); // MinMax[min=3500.0, max=7000.0]
Advertisement