5

I know we can use List.foreach to traverse,and we can use List.stream.foreach to traverse too. I do not understand which is better to traverse in Java8.

Rann Lifshitz
  • 4,040
  • 4
  • 22
  • 42
zzkyeee
  • 65
  • 6
  • `List.foreach` will do it without the overhead of streaming. – Andreas Jun 03 '18 at 02:05
  • It is better to use `List.forEach` when you simply want to iterate because it doesn't create an unnecessary `Stream`. – Slaw Jun 03 '18 at 02:05
  • @Andreas This is today,In Java8,List.foreach is also implemented in stream? – zzkyeee Jun 03 '18 at 02:08
  • @Slaw I don’t know which kind of performance is better – zzkyeee Jun 03 '18 at 02:10
  • If you're doing almost anything important with `List.stream().forEach()` ... then you're probably doing it wrong. – scottb Jun 03 '18 at 02:12
  • @scottb So what should I do when I want to iterate? – zzkyeee Jun 03 '18 at 02:17
  • forEach() is iteration disguised in Stream's clothing. If you want to iterate, then use a for each loop. It is a simple matter to write an adapter method for a stream to obtain an iterator for it. – scottb Jun 04 '18 at 00:58

2 Answers2

5

The forEach(Consumer) method is declared in the Iterable interface which Collection, and therefore List, extends. The default implementation of forEach(Consumer) is:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

As you can see the default implementation simply calls the action in a for-each loop. And a for-each loop is simply syntactic sugar for:

for (Iterator<?> iterator = iterable.iterator(); iterator.hasNext(); ) {
    Object element = iterator.next();
    // Do what you need to with element
} 

Except you don't have access to the Iterator in a for-each loop.

Specific implementations of Iterable may change how it actually iterates its elements (it may or may not use an Iterator) but it will almost always come down to some for or while loop. I say "almost always" because it's possible some type of recursion or chaining may be involved.

Now, using List.stream().forEach(Consumer) creates an unnecessary Stream object when you are simply trying to iterate the List sequentially. You should only use the streaming API if you actually need to process a collection of elements in a pipeline fashion (such as mapping, filtering, mapping some more, etc...).

So, for simply iterating, using List.stream().forEach(Consumer) is going to be less performant than a simple List.forEach(Consumer) call in virtually all cases. The performance increase will most likely be negligible but it is an easy enough fix that the "optimization" is not excessive; especially if you don't make the "mistake" in the first place. Don't create objects if you don't need them.

It may be better to simply use a for-each loop instead of forEach(Consumer) though. It can be easier to read than the more functional counterpart.


Edit

As mentioned in the comments by Holger, Stream.forEach(Consumer) has a pretty major difference to Iterable.forEach(Consumer): It does not guarantee the encounter order of the elements. While the iteration order of Iterable.forEach(Consumer) is not defined for the Iterable interface either, it can be defined by extending interfaces (such as List). When using a Stream, however, the order is not guaranteed regardless of the source of the Stream.

If you want the order to be guaranteed when using a Stream you have to use Stream.forEachOrdered(Consumer).

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • 2
    This is missing the most important point, the different semantics. `Stream.forEach` will iterate in an *unspecified order*, which may happen to match the encounter order in most of current implementations, but it is not guaranteed. Another one is that `forEach` on a synchronized collection may perform the entire iteration under the collection's lock which does not happen with `Stream.forEach`. – Holger Jun 03 '18 at 09:15
  • @Holger In a parallel stream what you mention is true all the time. For a sequential stream the ordering of `forEach` depends on the characteristics of the `Spliterator` (I'm assuming). All `List` implementations, for instance, will have a `Spliterator` that has the `Spliterator.ORDERED` characteristic (if they follow the contract). But an implementation of `Set` might not. In this regard the iteration order (or encounter order) is not any better defined for `Iterable.forEach` than it is for `Stream.forEach`. Again, this is for a sequential stream which this answer focused on. – Slaw Jun 03 '18 at 09:23
  • No, `Stream.forEach` is "explicitly nondeterministic". It is not required to respect any order, regardless of what characteristics the source has. Don't confuse the behavior of current implementations with guarantees of the specification. If you need an order, use `forEachOrdered` or stay with `Collection.forEach`. – Holger Jun 03 '18 at 09:28
  • @Holger Alright, I concede your point. I will say, however, I believe that an implementation that doesn't follow the iteration order of the source will have to be doing extra work to do so. It would have to do things like get the first element, get the second element, use the second element, then use the first element. Again, I'm only focusing on non-parallel streams when I say this. Edited my answer. – Slaw Jun 03 '18 at 09:35
  • 1
    Yes, it's hard to imagine a sequential execution scenario where the undefined iteration order could be exploited for an advantage and currently, the implementation just delegates to `Spliterator.forEachRemaining`, which doesn't even know whether it's serving `forEach` or `forEachOrdered`. But even if it stays a purely notional thing; `stream().forEach(action)` suggests to the future reader that `action` is order-independent, so it would be bad if it isn't.. – Holger Jun 03 '18 at 09:52
1

Note that although List.foreach looks similar to List.stream.foreach, it doesn't actually use streaming, so it will do it without the overhead of streaming.

To compare the complexity of the executed code, below I will show abbreviated version of how the two constructs work, simplified for clarity by removing validation logic.

`List.foreach`

E.g. in ArrayList, this is implemented as:

public void forEach(Consumer<? super E> action) {
    for (int i = 0; i < size(); i++)
        action.accept(get(i));
}

That's it. It's really that simple.

`List.stream.foreach`

This take multiple methods:

// In Collection
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

// In ArrayList
public Spliterator<E> spliterator() {
    return new ArrayListSpliterator(0, -1, 0);
}

// In StreamSupport
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
    return new ReferencePipeline.Head<>(spliterator,
                                        StreamOpFlag.fromCharacteristics(spliterator),
                                        parallel);
}

// In ReferencePipeline
public void forEach(Consumer<? super P_OUT> action) {
    evaluate(ForEachOps.makeRef(action, false));
}

// In ForEachOps
public static <T> TerminalOp<T, Void> makeRef(Consumer<? super T> action,
                                              boolean ordered) {
    return new ForEachOp.OfRef<>(action, ordered);
}

// many method calls eventually leading to

// In ArrayListSpliterator
public void forEachRemaining(Consumer<? super E> action) {
    for (int i = 0; i < size(); i++)
        action.accept(get(i));
}

As you can see, it runs through a lot more code, and creates at least 3 more objects.

Andreas
  • 154,647
  • 11
  • 152
  • 247