🎓 A1Learner
🔍
0 of 30 lessons completed

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?