I understand that with .stream()
, I can use chain operations like .filter()
or use parallel stream. But what is difference between them if I need to execute small operations (for example, printing the elements of the list)?
collection.stream().forEach(System.out::println); collection.forEach(System.out::println);
Advertisement
Answer
For simple cases such as the one illustrated, they are mostly the same. However, there are a number of subtle differences that might be significant.
One issue is with ordering. With Stream.forEach
, the order is undefined. It’s unlikely to occur with sequential streams, still, it’s within the specification for Stream.forEach
to execute in some arbitrary order. This does occur frequently in parallel streams. By contrast, Iterable.forEach
is always executed in the iteration order of the Iterable
, if one is specified.
Another issue is with side effects. The action specified in Stream.forEach
is required to be non-interfering. (See the java.util.stream package doc.) Iterable.forEach
potentially has fewer restrictions. For the collections in java.util
, Iterable.forEach
will generally use that collection’s Iterator
, most of which are designed to be fail-fast and which will throw ConcurrentModificationException
if the collection is structurally modified during the iteration. However, modifications that aren’t structural are allowed during iteration. For example, the ArrayList class documentation says “merely setting the value of an element is not a structural modification.” Thus, the action for ArrayList.forEach
is allowed to set values in the underlying ArrayList
without problems.
The concurrent collections are yet again different. Instead of fail-fast, they are designed to be weakly consistent. The full definition is at that link. Briefly, though, consider ConcurrentLinkedDeque
. The action passed to its forEach
method is allowed to modify the underlying deque, even structurally, and ConcurrentModificationException
is never thrown. However, the modification that occurs might or might not be visible in this iteration. (Hence the “weak” consistency.)
Still another difference is visible if Iterable.forEach
is iterating over a synchronized collection. On such a collection, Iterable.forEach
takes the collection’s lock once and holds it across all the calls to the action method. The Stream.forEach
call uses the collection’s spliterator, which does not lock, and which relies on the prevailing rule of non-interference. The collection backing the stream could be modified during iteration, and if it is, a ConcurrentModificationException
or inconsistent behavior could result.