Java Custom Collectors
A custom collector is a collector a program builds for a job the ready-made collectors do not cover. Collectors.teeing and Collector.of are the two building tools in this lesson.
teeing merges a count and a sum into an average
teeing sends the same scores to counting and to a summing collector in one pass. The merger divides the sum by the count to give the average.
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
double average = Stream.of(70, 80, 90)
.collect(Collectors.teeing(
Collectors.counting(),
Collectors.summingInt(Integer::intValue),
(count, sum) -> (double) sum / count));
System.out.println(average);
}
}Collector.of joins words inside brackets
This collector fills a StringJoiner, a class that joins text with a separator and a start and end mark. The finisher turns the joiner into the printed string.
import java.util.StringJoiner;
import java.util.stream.Collector;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Collector<String, StringJoiner, String> bracketJoiner = Collector.of(
() -> new StringJoiner(", ", "[", "]"),
StringJoiner::add,
StringJoiner::merge,
StringJoiner::toString);
String result = Stream.of("red", "green", "blue").collect(bracketJoiner);
System.out.println(result);
}
}teeing merges a smallest and a largest value
teeing can merge any two results, not only numbers for an average. Here the merger formats the smallest and largest scores as one range line.
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> scores = List.of(60, 75, 90);
String range = scores.stream()
.collect(Collectors.teeing(
Collectors.minBy(Comparator.comparingInt(Integer::intValue)),
Collectors.maxBy(Comparator.comparingInt(Integer::intValue)),
(min, max) -> min.orElse(0) + " to " + max.orElse(0)));
System.out.println(range);
}
}Example
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collector;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> scores = List.of(70, 80, 90);
double average = scores.stream()
.collect(Collectors.teeing(
Collectors.counting(),
Collectors.summingInt(Integer::intValue),
(count, sum) -> (double) sum / count));
System.out.println(average);
Collector<String, StringJoiner, String> bracketJoiner = Collector.of(
() -> new StringJoiner(", ", "[", "]"),
StringJoiner::add,
StringJoiner::merge,
StringJoiner::toString);
String label = List.of("midterm", "final").stream().collect(bracketJoiner);
System.out.println(label);
}
}Key points
- teeing runs two collectors over the same stream and merges their two results with a function.
- A supplier creates the empty container a collector fills.
- An accumulator folds one element into that container.
- A combiner merges two partly filled containers into one container.
- A finisher turns the filled container into the final result.
- Collector.of builds a collector from a supplier, an accumulator, a combiner and a finisher.
- A collector is a recipe of supplier, accumulator, combiner and finisher, and Collector.of writes that recipe down in code.
In this example, teeing averages three scores in one pass over the list. A hand-built Collector.of then joins two exam names inside brackets on the second line.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
Which part of a collector recipe creates the empty container the collector fills?