Iterating over a List
is not expensive per se. The expenses of repeated Stream operations depend on the actual repeated intermediate operations. Due to the optimizing environment, the code is executed in, two single-purpose loops may turn out to be more efficient than putting two concerns into one loop.
Note that the count does not depend on the result of the map
operation, hence, you can omit it for the count operation:
List<String> names = ...;
names.stream().filter(...).map(...).forEach(...);
// Then count them:
long count = names.stream().filter(...).count();
This is the variant to try and measure, for comparison with Stream approaches doing both actions at once.
Note that forEach
does not guaranty your action to be performed in order and in case of a parallel stream, it could even get evaluated concurrently. Similar behavior would apply to peek
, as well as any attempt to insert the actual action into a map
function.
An attempt to use something like
long count = names.stream().filter(...).map(...)
.map(x -> { // don't do this
// your action
return x;
}).count();
bears the additional problem that an implementation might consider the same thing as said above, the map
function’s result is irrelevant to the count, so it could get skipped, even if the current implementation does not.
Likewise, using
long count = names.stream().filter(...).map(...)
.peek(x -> { // don't do this
// your action
}).count();
bears some risk. The last map
step is irrelevant to the final count
, so it could get skipped, in which case the peek
action must get skipped too, as the input for it doesn’t exist. There is no guaranty that obsolete intermediate operations are kept only for the sake of peek
operations. We can only speculate, how radical the implementers will exploit this aspect. But the only practical example in the current reference implementation, is the skipping of the entire pipeline when the count is predictable beforehand, which also skips all peek
operations. This suggests that we should never rely on the execution of peek
actions.
But when you use
int count = names.stream().filter(...).map(...)
.mapToInt(x -> { // don't assume sequential in-order execution
// your action
return 1;
}).sum();
the end result is dependent on the function containing the action, so it will always be evaluated. But unlike forEach
, which can be replaced by forEachOrdered
, to get guaranteed non-concurrent, ordered execution, there is no such option for the mapping function.