Consumer Action on Stream Elements

We have already used both the forEach() and forEachOrdered() terminal operations to print elements when the pipeline is executed. These operations allow side effects on stream elements.

The forEach() method is defined for both streams and collections. In the case of collections, the method iterates over all the elements in the collection, whereas it is a terminal operation on streams.

Since these terminal operations perform an action on each element, the input stream to the operation must be finite in order for the operation to terminate.

Counterparts to the forEach() and forEachOrdered() methods for the primitive numeric types are also defined by the numeric stream interfaces.

Click here to view code image

void forEach(Consumer<? super T> action)

This terminal operation performs an action on each element of this stream. This method should not be relied upon to produce deterministic results, as the order in which the elements are processed is not guaranteed.

Click here to view code image

void forEachOrdered(Consumer<? super T> action)

This terminal operation performs an action on each element of this stream, but in the encounter order of the stream if the stream has one.

The difference in behavior of the forEach() and forEachOrdered() terminal operations is that the forEach() method does not guarantee to respect the encounter order, whereas the forEachOrdered() method always does, if there is one.

Each operation is applied to both an ordered sequential stream and an ordered parallel stream to print CD titles with the help of the consumer printStr:

Click here to view code image

Consumer<String> printStr = str -> System.out.print(str + “|”);
CD.cdList.stream().map(CD::title).forEach(printStr);               // (1a)
//Java Jive|Java Jam|Lambda Dancing|Keep on Erasing|Hot Generics|
CD.cdList.stream().parallel().map(CD::title).forEach(printStr);    // (1b)
//Lambda Dancing|Hot Generics|Keep on Erasing|Java Jam|Java Jive|

The behavior of the forEach() operation is nondeterministic, as seen at (1a) and (1b). The output from (1a) and (1b) shows that the forEach() operation respects the encounter order for an ordered sequential stream, but not necessarily for an ordered parallel stream. Respecting the encounter order for an ordered parallel stream would incur overhead that would impact performance, and is therefore ignored.

On the other hand, the forEachOrdered() operation always respects the encounter order in both cases, as seen below from the output at (2a) and (2b). However, it is important to note that, in the case of the ordered parallel stream, the terminal action on the elements can be executed in different threads, but guarantees that the action is applied to the elements in encounter order.

Click here to view code image

CD.cdList.stream().map(CD::title).forEachOrdered(printStr);              // (2a)
//Java Jive|Java Jam|Lambda Dancing|Keep on Erasing|Hot Generics|
CD.cdList.stream().parallel().map(CD::title).forEachOrdered(printStr);   // (2b)
//Java Jive|Java Jam|Lambda Dancing|Keep on Erasing|Hot Generics|

The discussion above also applies when the forEach() and forEachOrdered() terminal operations are invoked on numeric streams. The nondeterministic behavior of the forEach() terminal operation for int streams is illustrated below. The terminal operation on the sequential int stream at (3a) seems to respect the encounter order, but should not be relied upon. The terminal operation on the parallel int stream at (3b) can give different results for different runs.

Click here to view code image

IntConsumer printInt = n -> out.print(n + “|”);
IntStream.of(2018, 2019, 2020, 2021, 2022).forEach(printInt);            // (3a)
//2018|2019|2020|2021|2022|
IntStream.of(2018, 2019, 2020, 2021, 2022).parallel().forEach(printInt); // (3b)
//2020|2019|2018|2021|2022|

Leave a Reply

Your email address will not be published. Required fields are marked *