8238286: Add new flatMap stream operation that is more amenable to pushing

This patch adds a new flatmap-like operation called mapMulti to the java.util.Stream class as well as the primitive variations of this operation i.e. mapMultiToInt, IntStream mapMulti, etc.

Reviewed-by: psandoz, smarks
This commit is contained in:
Patrick Concannon 2020-08-31 16:12:32 +01:00
parent dd89c92c50
commit 79d12507b3
10 changed files with 899 additions and 15 deletions

View file

@ -26,20 +26,17 @@ package java.util.stream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.LongConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
@ -279,6 +276,7 @@ public interface Stream<T> extends BaseStream<T, Stream<T>> {
* function to apply to each element which produces a stream
* of new values
* @return the new stream
* @see #mapMulti
*/
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
@ -342,6 +340,210 @@ public interface Stream<T> extends BaseStream<T, Stream<T>> {
*/
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
/**
* Returns a stream consisting of the results of replacing each element of
* this stream with multiple elements, specifically zero or more elements.
* Replacement is performed by applying the provided mapping function to each
* element in conjunction with a {@linkplain Consumer consumer} argument
* that accepts replacement elements. The mapping function calls the consumer
* zero or more times to provide the replacement elements.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* <p>If the {@linkplain Consumer consumer} argument is used outside the scope of
* its application to the mapping function, the results are undefined.
*
* @implSpec
* The default implementation invokes {@link #flatMap flatMap} on this stream,
* passing a function that behaves as follows. First, it calls the mapper function
* with a {@code Consumer} that accumulates replacement elements into a newly created
* internal buffer. When the mapper function returns, it creates a stream from the
* internal buffer. Finally, it returns this stream to {@code flatMap}.
*
* @apiNote
* This method is similar to {@link #flatMap flatMap} in that it applies a one-to-many
* transformation to the elements of the stream and flattens the result elements
* into a new stream. This method is preferable to {@code flatMap} in the following
* circumstances:
* <ul>
* <li>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 {@code flatMap}.</li>
* <li>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.</li>
* </ul>
*
* <p>If a lambda expression is provided as the mapper function argument, additional type
* information maybe be necessary for proper inference of the element type {@code <R>} of
* the returned stream. This can be provided in the form of explicit type declarations for
* the lambda parameters or as an explicit type argument to the {@code mapMulti} call.
*
* <p><b>Examples</b>
*
* <p>Given a stream of {@code Number} objects, the following
* produces a list containing only the {@code Integer} objects:
* <pre>{@code
* Stream<Number> numbers = ... ;
* List<Integer> integers = numbers.<Integer>mapMulti((number, consumer) -> {
* if (number instanceof Integer)
* consumer.accept((Integer) number);
* })
* .collect(Collectors.toList());
* }</pre>
*
* <p>If we have an {@code Iterable<Object>} and need to recursively expand its elements
* that are themselves of type {@code Iterable}, we can use {@code mapMulti} as follows:
* <pre>{@code
* class C {
* static void expandIterable(Object e, Consumer<Object> c) {
* if (e instanceof Iterable) {
* for (Object ie: (Iterable<?>) e) {
* expandIterable(ie, c);
* }
* } else if (e != null) {
* c.accept(e);
* }
* }
*
* public static void main(String[] args) {
* Stream<Object> stream = ...;
* Stream<Object> expandedStream = stream.mapMulti(C::expandIterable);
* }
* }
* }</pre>
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function that generates replacement elements
* @return the new stream
* @see #flatMap flatMap
* @since 16
*/
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);
});
}
/**
* Returns an {@code IntStream} consisting of the results of replacing each
* element of this stream with multiple elements, specifically zero or more
* elements.
* Replacement is performed by applying the provided mapping function to each
* element in conjunction with a {@linkplain IntConsumer consumer} argument
* that accepts replacement elements. The mapping function calls the consumer
* zero or more times to provide the replacement elements.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* <p>If the {@linkplain IntConsumer consumer} argument is used outside the scope of
* its application to the mapping function, the results are undefined.
*
* @implSpec
* The default implementation invokes {@link #flatMapToInt flatMapToInt} on this stream,
* passing a function that behaves as follows. First, it calls the mapper function
* with an {@code IntConsumer} that accumulates replacement elements into a newly created
* internal buffer. When the mapper function returns, it creates an {@code IntStream} from
* the internal buffer. Finally, it returns this stream to {@code flatMapToInt}.
*
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function that generates replacement elements
* @return the new stream
* @see #mapMulti mapMulti
* @since 16
*/
default IntStream mapMultiToInt(BiConsumer<? super T, ? super IntConsumer> mapper) {
Objects.requireNonNull(mapper);
return flatMapToInt(e -> {
SpinedBuffer.OfInt buffer = new SpinedBuffer.OfInt();
mapper.accept(e, buffer);
return StreamSupport.intStream(buffer.spliterator(), false);
});
}
/**
* Returns a {@code LongStream} consisting of the results of replacing each
* element of this stream with multiple elements, specifically zero or more
* elements.
* Replacement is performed by applying the provided mapping function to each
* element in conjunction with a {@linkplain LongConsumer consumer} argument
* that accepts replacement elements. The mapping function calls the consumer
* zero or more times to provide the replacement elements.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* <p>If the {@linkplain LongConsumer consumer} argument is used outside the scope of
* its application to the mapping function, the results are undefined.
*
* @implSpec
* The default implementation invokes {@link #flatMapToLong flatMapToLong} on this stream,
* passing a function that behaves as follows. First, it calls the mapper function
* with a {@code LongConsumer} that accumulates replacement elements into a newly created
* internal buffer. When the mapper function returns, it creates a {@code LongStream} from
* the internal buffer. Finally, it returns this stream to {@code flatMapToLong}.
*
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function that generates replacement elements
* @return the new stream
* @see #mapMulti mapMulti
* @since 16
*/
default LongStream mapMultiToLong(BiConsumer<? super T, ? super LongConsumer> mapper) {
Objects.requireNonNull(mapper);
return flatMapToLong(e -> {
SpinedBuffer.OfLong buffer = new SpinedBuffer.OfLong();
mapper.accept(e, buffer);
return StreamSupport.longStream(buffer.spliterator(), false);
});
}
/**
* Returns a {@code DoubleStream} consisting of the results of replacing each
* element of this stream with multiple elements, specifically zero or more
* elements.
* Replacement is performed by applying the provided mapping function to each
* element in conjunction with a {@linkplain DoubleConsumer consumer} argument
* that accepts replacement elements. The mapping function calls the consumer
* zero or more times to provide the replacement elements.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* <p>If the {@linkplain DoubleConsumer consumer} argument is used outside the scope of
* its application to the mapping function, the results are undefined.
*
* @implSpec
* The default implementation invokes {@link #flatMapToDouble flatMapToDouble} on this stream,
* passing a function that behaves as follows. First, it calls the mapper function
* with an {@code DoubleConsumer} that accumulates replacement elements into a newly created
* internal buffer. When the mapper function returns, it creates a {@code DoubleStream} from
* the internal buffer. Finally, it returns this stream to {@code flatMapToDouble}.
*
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function that generates replacement elements
* @return the new stream
* @see #mapMulti mapMulti
* @since 16
*/
default DoubleStream mapMultiToDouble(BiConsumer<? super T, ? super DoubleConsumer> mapper) {
Objects.requireNonNull(mapper);
return flatMapToDouble(e -> {
SpinedBuffer.OfDouble buffer = new SpinedBuffer.OfDouble();
mapper.accept(e, buffer);
return StreamSupport.doubleStream(buffer.spliterator(), false);
});
}
/**
* Returns a stream consisting of the distinct elements (according to
* {@link Object#equals(Object)}) of this stream.