Intermediate Operations

An intermediate operation is an operation that allows further operations to be added to a Stream. For example, filter is an intermediate operation since additional operations can be added to a Stream pipeline after filter has been applied to the Stream.

In the examples below, many of the lambdas could be replaced by method references (e.g. the lambda () → new StringBuilder can be replaced by a method reference StringBuilder::new).

Common Operations

The following intermediate operations are accepted by a Stream:

Operation Parameter Returns a Stream that:

filter

Predicate

Contains only the elements that match the Predicate

map

Function

Contains the results of applying the given Function to the elements of this stream

distinct

-

Contains the distinct (i.e. unique) elements in the stream as per the element’s equals() method

sorted

-

Contains the elements in the stream in sorted order as per the element’s compareTo() method

sorted

Comparator

Contains the elements in the stream in sorted order as per the given Comparator

limit

long

Contains the original elements in the stream but truncated to be no longer than the given long value

skip

long

Contains the original elements in the stream but after discarding the given long value of elements

flatMap

Function

Contains the elements of the Stream`s in this stream obtained by applying the given `Function to the stream elements of this stream

peek

Consumer

Contains the original elements in the stream but additionally letting the given Consumer accept each element (side effect)

Below are examples that demonstrate how these operations can be applied.

filter()

Stream.of("B", "A", "C" , "B")
    .filter(s -> s.equals("B"))

Returns a Stream with the elements "B" and "B" because only elements that are equal to "B" will pass the filter operation.

map()

Stream.of("B", "A", "C" , "B")
    .map(s -> s.toLowerCase())

Returns a Stream with the elements "b", "a", "c" and "b" because each element will be mapped (converted) to its lower case representation.

distinct()

Stream.of("B", "A", "C" , "B")
    .distinct()

Returns a Stream with the elements "B", "A" and "C" because only unique elements will pass the distinct operation.

sorted()

Stream.of("B", "A", "C" , "B")
    .sorted()

Returns a Stream with the elements "A", "B", "B" and "C" because the sort operation will sort all elements in the stream in natural order.

Stream.of("B", "A", "C" , "B")
    .sorted(Comparator.reverseOrder())

Returns a Stream with the elements "C", "B", "B" and "A" because the sort operation will sort all elements in the stream according to the provided Comparator (in reversed natural order).

limit()

Stream.of("B", "A", "C" , "B")
    .limit(2)

Returns a Stream with the elements "B" and "A" because after the two first elements the rest of the elements will be discarded.

skip()

Stream.of("B", "A", "C" , "B")
    .skip(1)

Returns a Stream with the elements "A", "C" and "B" because the first element in the stream will be skipped.

flatMap()

Stream.of(
    Stream.of("B", "A"),
    Stream.of("C", "B")
)
    .flatMap(Function.identity())
    .forEachOrdered(System.out::println);

Returns a Stream with the elements "B", "A", "C" and "B" because the two streams (that each contain two elements) are "flattened" to a single Stream with four elements.

Stream.of(
    Arrays.asList("B", "A"),
    Arrays.asList("C", "B")
)
    .flatMap(l -> l.stream())

Returns a Stream with the elements "B", "A", "C" and "B" because the two lists (that each contain two elements) are "flattened" to a single Stream with four elements. The lists are converted to sub-streams using the List::stream mapper method.

peek()

Stream.of("B", "A", "C" , "B")
    .peek(System.out::print)

Returns a Stream with the elements "B", "A", "C" and "B" but, when consumed in its entirety, will print out the text "BACB" as a side effect.

Side-effect usage is discouraged in Streams. Use this operation for debug only.

Stream Property Operations

There are also a number of intermediate operations that controls the properties of the Stream and has no effect on its actual content. These are:

Operation Parameter Returns a Stream that:

parallel

-

is parallel (not sequential)

sequential

-

is sequential (not parallel)

unordered

-

is unordered (data might appear in any order)

onClose

Runnable

will run the provided Runnable when closed

parallel()

Stream.of("B", "A", "C" , "B")
    .parallel()

Returns a Stream with the elements "B", "A", "C" and "B" but, when consumed, elements in the Stream may be propagated through the pipeline using different Threads. By default, parallel streams are executed on the default `ForkJoinPool.

sequential()

Stream.of("B", "A", "C" , "B")
    .parallel()
    .sequential()

Returns a Stream with the elements "B", "A", "C" and "B" that is not parallel.

unordered()

Stream.of("B", "A", "C" , "B")
    .unordered()

Returns a Stream with the given elements but not necessary in any particular order. So when consumed, elements might be encountered in any order, for example in the order "C", "B", "B", "A". Note that unordered is just a relaxation of the stream requirements. Unordered streams can retain their original element order or elements can appear in any other order.

onClose()

Stream.of("B", "A", "C", "B")
    .onClose( () -> System.out.println("The Stream was closed") );

Is a Stream with the elements "B", "A", "C" and "B" but, when closed, will print out the text "The Stream was closed".

Map to Primitive Operations

There are also some intermediate operations that maps a Stream to one of the special primitive stream types; IntStrem, LongStream and DoubleStream:

Operation Parameter Returns a Stream that:

mapToInt

ToIntFunction

Is an IntStream containing int elements obtained by applying the given ToIntFunction to the elements of this stream

mapToLong

ToLongFunction

Is a LongStream containing long elements obtained by applying the given ToLongFunction to the elements of this stream

mapToDouble

ToDoubleFunction

Is a DoubleStream containing double elements obtained by applying the given ToDoubleFunction to the elements of this stream

flatMapToInt

Function

Contains the int elements of the IntStream`s in this stream obtained by applying the given `Function to the stream elements of this stream

flatMapToLong

Function

Contains the long elements of the LongStream`s in this stream obtained by applying the given `Function to the stream elements of this stream

flatMapToDouble

Function

Contains the double elements of the DoubleStream`s in this stream obtained by applying the given `Function to the stream elements of this stream

In many cases, primitive streams provide better performance but can only handle streams of: int, long and double.

mapToInt()

Stream.of("B", "A", "C" , "B")
    .mapToInt(s -> s.hashCode())

Returns an IntStream with the int elements 66, 65, 67 and 66. (A is 65, B is 66 and so on)

mapToLong()

Stream.of("B", "A", "C", "B")
    .mapToLong(s -> s.hashCode() * 1_000_000_000_000l)

Returns a LongStream with the long elements 66000000000000, 65000000000000, 67000000000000 and 66000000000000.

mapToDouble()

Stream.of("B", "A", "C", "B")
    .mapToDouble(s -> s.hashCode() / 10.0)

Returns a DoubleStream with the double elements 6.6, 6.5, 6.7 and 6.6. === flatMapToInt

Stream.of(
    IntStream.of(1, 2),
    IntStream.of(3, 4)
)
    .flatMapToInt(s -> s.map(i -> i + 1))

Returns an IntStream with the int elements 2, 3, 4 and 5 because the two `IntStream`s where flattened to one stream whereby 1 was added to each element.

flatMapToLong()

Stream.of(
    LongStream.of(1, 2),
    LongStream.of(3, 4)
)
    .flatMapToLong(s -> s.map(i -> i + 1))

Returns a LongStream with the long elements 2, 3, 4 and 5 because the two LongStreams where flattened to one stream whereby 1 was added to each element.

flatMapToDouble()

Stream.of(
    DoubleStream.of(1.0, 2.0),
    DoubleStream.of(3.0, 4.0)
)
    .flatMapToDouble(s -> s.map(i -> i + 1))

Returns a DoubleStream with the double elements 2.0, 3.0, 4.0 and 5.0 because the two `DoubleStream`s where flattened to one stream whereby 1 was added to each element.

Map Multi Operations (Java 16 Only)

Java 16 introduced several new map operations, expanding the capabilities of Streams. The mapMulti() function enables the mapping of a single element within the Stream to zero or multiple elements, contingent on specific conditions.

Operation Parameter Returns a Stream that:

mapMulti

BiConsumer

Returns a stream consisting of the results of replacing each element of this stream with multiple elements, specifically zero or more elements.

mapMultiToDouble

BiConsumer

Returns a DoubleStream consisting of the results of replacing each element of this stream with multiple elements, specifically zero or more elements.

mapMultiToInt

BiConsumer

Returns an IntStream consisting of the results of replacing each element of this stream with multiple elements, specifically zero or more elements.

mapMultiToLong

BiConsumer

Returns a LongStream consisting of the results of replacing each element of this stream with multiple elements, specifically zero or more elements.

These operations are only available in applications running Java 16 or later.

mapMulti()

Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
    .mapMulti((i, mapper) -> {
        if (i % 2 == 0) {
            mapper.accept(i);
            mapper.accept(i);
        }
    });

Returns a Stream with the elements [2.0, 2.0, 4.0, 4.0] because the even elements were duplicated by accepting them twice.

mapMultiToDouble()

Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
    .mapMulti((i, mapper) -> {
        if (i % 2 == 0) {
            mapper.accept(i);
            mapper.accept(i);
        }
    });

Returns an DoubleStream with the elements [2.0, 2.0, 4.0, 4.0] because the even elements were duplicated by accepting them twice.

mapMultiToInt()

Stream.of(1, 2, 3, 4, 5)
    .mapMulti((i, mapper) -> {
        if (i % 2 == 0) {
            mapper.accept(i);
            mapper.accept(i);
        }
    });

Returns an IntStream with the elements [2, 2, 4, 4] because the even elements were duplicated by accepting them twice.

mapMultiToLong()

Stream.of(1.0, 2.0, 3.0, 4.0, 5.0)
    .mapMulti((i, mapper) -> {
        if (i % 2 == 0) {
            mapper.accept(i.longValue());
            mapper.accept(i.longValue());
        }
    });

Returns an LongStream with the elements [2, 2, 4, 4] because the even elements were duplicated by accepting them twice.

Primitive Operations

Primitive streams (like IntStream and LongStream) provide similar functionality as ordinary streams but usually the parameter count and types differ so that primitive streams can accept more optimized function variants. Here is a table of some additional Intermediate Operations that primitive Streams can take:

Operation Parameter Returns a Stream that:

boxed

-

contains the boxed elements in the original stream (e.g. an int is boxed to an Integer)

asLongStream

-

contains the elements in the original stream converted to long elements

asDoubleStream

-

contains the elements in the original stream converted to double elements

boxed()

IntStream.of(1, 2, 3, 4)
    .boxed()

returns a Stream with the Integer elements 1, 2, 3 and 4 because the original int elements were boxed to their corresponding Integer elements.

asLongStream()

IntStream.of(1, 2, 3, 4)
    .asLongStream()

returns a LongStream with the long elements 1, 2, 3 and 4 because the original int elements were converted to long elements.

asDoubleStream()

IntStream.of(1, 2, 3, 4)
    .asDoubleStream()

returns a DoubleStream with the double elements 1.0, 2.0, 3.0 and 4.0 because the original int elements were converted to double elements.

Selective Operations

Two stream operations exist for capturing or omitting elements within the stream until a particular condition is satisfied. Unlike a filter, these operations discontinue predicate evaluation once it turns true for the first time.

Operation Parameter Returns a Stream that:

takeWhile

Predicate

Contains the elements in the original stream until the the first one fails the Predicate test

dropWhile

Predicate

Contains the elements in the original stream dropping all elements until the the first one fails the Predicate test then containing the rest of the elements

takeWhile()

Stream.of("B", "A", "C", "B")
    .takeWhile(s -> "B".compareTo(s) >= 0)

Returns a Stream with the elements "B" and "A" because when "C" is encountered in the Stream, that element and all following are dropped.

dropWhile()

Stream.of("B", "A", "C", "B")
    .dropWhile(s -> "B".compareTo(s) >= 0)

Returns a Stream with the elements "C" and "B" because elements are dropped from the Stream but when "C" in encountered, subsequent elements are not dropped.