Reputation: 467
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
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 Bar
s 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
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