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)?
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
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.