geofflittle
geofflittle

Reputation: 467

Mapping Over an Ordered List in java 8

In the streaming library included with Java 8, we're provided with the forEachOrdered API whose documentation is reproduced here

void forEachOrdered(Consumer action)

Performs an action for each element of this stream.

This is a terminal operation.

This operation processes the elements one at a time, in encounter order if one exists. Performing the action for one element happens-before performing the action for subsequent elements, but for any given element, the action may be performed in whatever thread the library chooses.

Do we have any guarantees in terms of ordering from the map API?

For example, I have the following in code

private final BazUtils bazUtils;

public List<Foo> getFoos(List<Bar> bars) {
    AtomicInteger idx = new AtomicInteger(1);
    return bars.stream()
        .map(bar -> bazUtils.getFoo(bar, idx.getAndAdd(1)))
        .collect(Collectors.toList());
}

Will the order of bars be respected in processing?

Upvotes: 1

Views: 276

Answers (2)

Holger
Holger

Reputation: 298153

See the package documentation, sections “Stateless behaviors” and “Side-effects”:

…If the behavioral parameters do have side-effects, unless explicitly stated, there are no guarantees as to the visibility of those side-effects to other threads, nor are there any guarantees that different operations on the "same" element within the same stream pipeline are executed in the same thread. Further, the ordering of those effects may be surprising. Even when a pipeline is constrained to produce a result that is consistent with the encounter order of the stream source (for example, IntStream.range(0,5).parallel().map(x -> x*2).toArray() must produce [0, 2, 4, 6, 8]), no guarantees are made as to the order in which the mapper function is applied to individual elements, or in what thread any behavioral parameter is executed for a given element

In other words, your example won’t work when using a parallel stream, even using an AtomicInteger as there is no guaranty regarding the processing order. The stream will maintain the encounter order, i.e. the resulting List’s order will reflect the original order of the Bars in the original List but the int values generated via a side effect may not match (and the parallel performance might be poor, by the way).

The forEachOrdered terminal action is very special in that the action’s processing order will match the encounter order. This will limit the benefit of parallel processing dramatically when using this terminal action.

You can do what you want correctly using

public List<Foo> getFoos(List<Bar> bars) {
    return IntStream.range(0, bars.size()) //you may add .parallel()
        .mapToObj(idx -> bazUtils.getFoo(bars.get(idx), idx+1))
        .collect(Collectors.toList());
}

Upvotes: 2

Louis Wasserman
Louis Wasserman

Reputation: 198083

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#StreamOps

If a stream is ordered, most operations are constrained to operate on the elements in their encounter order; if the source of a stream is a List containing [1, 2, 3], then the result of executing map(x -> x*2) must be [2, 4, 6]. However, if the source has no defined encounter order, then any permutation of the values [2, 4, 6] would be a valid result.

So, yes; order is preserved by map.

Upvotes: 3

Related Questions