Saturday, August 14, 2021

Java 16: Stream.mapMulti

Java 16 introduces a new Stream.mapMulti method which allows you to replace elements in a stream with multiple elements.

The example below shows how you can use mapMulti to replace each string in a stream with its uppercased and lowercased versions:

Stream.of("Twix", "Snickers", "Mars")
  .mapMulti((s, c) -> {
    c.accept(s.toUpperCase());
    c.accept(s.toLowerCase());
  })
  .forEach(System.out::println);

Output:
TWIX
twix
SNICKERS
snickers
MARS
mars

The same thing can also be achieved using flatMap like this:

Stream.of("Twix", "Snickers", "Mars")
  .flatMap(s -> Stream.of(s.toUpperCase(), s.toLowerCase()))
  .forEach(System.out::println);

So what is the difference between mapMulti and flatMap? According to the javadocs:

This method is preferable to flatMap in the following circumstances:

  • When replacing each stream element with a small (possibly zero) number of elements. Using this method avoids the overhead of creating a new Stream instance for every group of result elements, as required by flatMap.
  • When it is easier to use an imperative approach for generating result elements than it is to return them in the form of a Stream.

Inspecting the code for multiMap, we can see that it delegates to flatMap, however, it makes use of a SpinedBuffer to hold the elements before creating the stream, thus avoiding the overhead of creating new streams per group of result elements.

default <R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper) {
  Objects.requireNonNull(mapper);
  return flatMap(e -> {
    SpinedBuffer<R> buffer = new SpinedBuffer<>();
    mapper.accept(e, buffer);
    return StreamSupport.stream(buffer.spliterator(), false);
  });
}