Implementing an ImmutableMap collector

March 26, 2016

Creating an ImmutableMap Collector for a Java8 Stream

Implementing a new Stream collector can seem a little daunting, the interface feature mutliple type parameters and the javadoc is a wall of text. Nevertheless it is required if you want to collect your stream into a collection not found in the standard library, like Guava immutable collections.

Fortunately implementing a new collector is not as much work as it might seem. There is a static factory method that keeps the boilerplate to a minimum, so it is largely a matter of getting your head around the type signitures and required behaviour of the functions requried to contstruct a new collector.

Type Parameters

  • T - The type of input elements for the new collector
  • A - The intermediate accumulation type of the new collector
  • R - The final result type of the new collector

Required Functions

  • A Supplier (() -> A) that returns a new mutable result container
  • A Accumulator ((A,T) -> void) that accepts a reuslt container and a stream element and adds the element to the container. This returns void as it is intended to operate via mutation
  • A Combiner ((A,A) -> A) that combines two result containers into a single container
  • A Finisher (A -> R) that optionally applies a final transformation to the result container producing the final return type of the collector

implementing toImmutableMap

As a concrete example lets implement an immutable counterpart to Collectors#toMap. This method takes two functions,

  • keyMapper (T -> K)
  • valueMapper (T -> V)

and outputs a collector that will collects a stream into a Map of the output of applying the functions to each element in the stream.

The first thing we need to our immutableMap implementation is a supplier, in this instance we want to use a builder as our intermediate result container

ImmutableMap::Builder

For our accumulator we need to apply our keyMapper and valueMapper for element and then put the results into our builder

(builder, element) -> builder.put(keyMapper.apply(element), valueMapper.apply(element))

For the combiner we can just use a naive implementation as I primarily have serial streams in mind for this collector. We can build on of the builders into a Map and then use putAll to the contents to the other builder.

(builderOne, builderTwo) -> builderOne.putAll(builderTwo.build())

Our finisher is simply a call to build to produce the desired output type, ImmutableMap

Builder::build

Putting all of the pieces together we get the following method