Writing a Custom Collector

Streams · lesson 23 of 42 · 4 min read

The four functions behind every collector, and how to assemble your own when none of them fit.

Open this lesson in the learning hub

Key points

  • supplier makes the container, accumulator adds one element, combiner merges containers, finisher converts it.
  • Collector.of(...) builds one inline. The combiner is used only by parallel streams, but it must still be correct.
  • The three argument collect(supplier, accumulator, combiner) skips the Collector object entirely for one off cases.
  • collectingAndThen wraps a finishing step around a collector you already have, such as freezing a list.
  • Reach for a custom collector only when nesting the built in ones stops reading clearly. Most needs are already covered.

Example

import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("ada", "grace", "alan");

        // Four parts: supplier, accumulator, combiner, finisher.
        Collector<String, StringJoiner, String> banner = Collector.of(
                () -> new StringJoiner(" | ", "<", ">"),
                (joiner, name) -> joiner.add(name.toUpperCase()),
                StringJoiner::merge,
                StringJoiner::toString);

        System.out.println("custom   : " + names.stream().collect(banner));
        System.out.println("parallel : " + names.parallelStream().collect(banner));

        // The same three functions inline, with no Collector object at all.
        String inline = names.stream()
                .collect(StringBuilder::new, (sb, n) -> sb.append(n).append(';'), StringBuilder::append)
                .toString();
        System.out.println("3-arg    : " + inline);

        // collectingAndThen wraps a finishing step around a collector you already have.
        List<String> frozen = names.stream().collect(
                Collectors.collectingAndThen(Collectors.toCollection(ArrayList::new), List::copyOf));
        System.out.println("frozen   : " + frozen);
        try {
            frozen.add("linus");
        } catch (UnsupportedOperationException e) {
            System.out.println("frozen   : and it really is immutable");
        }
    }
}

Supplier, accumulator, combiner, finisher. Learn those four and every collector makes sense.

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.