Java Streams Collector Operations Explained with Examples
Collectors are the workhorses of Stream terminal aggregation in Java 8. They let you join strings, group and partition data, build specific collection types, and compute statistics — all in a fluent, readable style.
1) Collectors.joining() — Concatenate elements
Concatenate stream elements into a single string with optional delimiter, prefix, and suffix.
List<String> words = Arrays.asList("apple", "banana", "cherry");
String joined = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(joined); // [apple, banana, cherry]
2) Collectors.groupingBy() — Group by classifier
Group elements based on a key extractor. Default downstream is toList().
List<Person> people = Arrays.asList(
new Person("John", "New York"),
new Person("Wick", "London"),
new Person("Smith", "New York")
);
Map<String, List<Person>> byCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
System.out.println(byCity);
2.1) groupingBy with a downstream collector
Count how many people per city using counting(). You can plug in many downstream collectors like mapping, toSet, etc.
Map<String, Long> countsPerCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
System.out.println(countsPerCity); // {London=1, New York=2}
2.2) groupingBy with custom map type + downstream
Produce a TreeMap of cities to uppercase names list, preserving key order.
Map<String, List<String>> namesByCity = people.stream()
.collect(Collectors.groupingBy(
Person::getCity,
java.util.TreeMap::new,
Collectors.mapping(p -> p.getName().toUpperCase(), Collectors.toList())
));
System.out.println(namesByCity);
3) Collectors.partitioningBy() — Split into true/false buckets
Partition elements by a predicate (exactly two groups).
List<Integer> nums = Arrays.asList(1,2,3,4,5,6);
Map<Boolean, List<Integer>> evensAndOdds = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(evensAndOdds); // {false=[1, 3, 5], true=[2, 4, 6]}
4) Numeric aggregations — summingInt, averagingInt, summarizingInt
Quick math over a stream of numbers.
List<Integer> vals = Arrays.asList(1,2,3,4,5);
int sum = vals.stream().collect(Collectors.summingInt(n -> n));
double avg = vals.stream().collect(Collectors.averagingInt(n -> n));
java.util.IntSummaryStatistics stats = vals.stream()
.collect(Collectors.summarizingInt(n -> n));
System.out.println(sum); // 15
System.out.println(avg); // 3.0
System.out.println(stats); // count=5, sum=15, min=1, average=3.0, max=5
5) Collectors.mapping() — Transform before collecting
Apply a mapping function as part of the collect phase (often with groupingBy).
List<String> upperNames = people.stream()
.collect(Collectors.mapping(p -> p.getName().toUpperCase(), Collectors.toList()));
System.out.println(upperNames); // [JOHN, WICK, SMITH]
6) Building collections — toList, toSet, toCollection
Choose the target collection type explicitly.
List<Integer> raw = Arrays.asList(1,1,2,3,4,4,5);
// Set (remove duplicates, no defined iteration order)
java.util.Set<Integer> asSet = raw.stream().collect(Collectors.toSet());
// Specific collection type (LinkedList here)
java.util.LinkedList<Integer> linked = raw.stream()
.collect(Collectors.toCollection(java.util.LinkedList::new));
System.out.println(asSet);
System.out.println(linked);
7) Collectors.toMap() — Build a Map (with merge + map type)
When keys may collide, supply a merge function; you can also pick the map implementation.
// Name -> City (merging keeps the first value for duplicate names)
java.util.Map<String, String> nameToCity = people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getCity,
(left, right) -> left // merge on duplicate key
));
// Preserve insertion order with LinkedHashMap
java.util.Map<String, String> ordered = people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getCity,
(l, r) -> l,
java.util.LinkedHashMap::new
));
System.out.println(nameToCity);
System.out.println(ordered);
8) Collectors.counting() — Count elements
Standalone or as a downstream (e.g., with groupingBy).
List<String> letters = Arrays.asList("a","bb","ccc","dddd");
long total = letters.stream().collect(Collectors.counting());
System.out.println(total); // 4
Person class (used in examples)
class Person {
private String name;
private String city;
Person(String name, String city) {
this.name = name;
this.city = city;
}
public String getName() { return name; }
public String getCity() { return city; }
@Override public String toString() {
return "Person{" +
"city='" + city + '\'' +
", name='" + name + '\'' +
'}';
}
}
When to use what?
- joining → build one string from many values
- groupingBy → bucketize by key; combine with downstreams like counting, mapping
- partitioningBy → boolean split (true/false)
- summing/averaging/summarizing → quick number stats
- toSet/toCollection → pick the exact collection type
- toMap → control key collisions and map implementation
- counting → element totals or per-group counts
All examples are Java 8 compatible. Pair these collectors with intermediate ops (map, filter, flatMap) to build expressive, high-performance pipelines.
Labels: Java Streams, Java 8, Collectors, groupingBy, partitioningBy, toMap, joining, summarizingInt, mapping, toSet, toCollection, Stream API, DevForgeHub
Comments
Post a Comment