The Difference Between Collection.stream().forEach() and Collection.forEach() – Collection.stream().forEach()与Collection.forEach()的区别

最后修改: 2019年 6月 30日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

There are several options to iterate over a collection in Java. In this short tutorial, we’ll look at two similar looking approaches — Collection.stream().forEach() and Collection.forEach().

在Java中,有几种选择来迭代一个集合。在这个简短的教程中,我们将看看两种看起来相似的方法 – Collection.stream().forEach()Collection.forEach()

In most cases, both will yield the same results, but we’ll look at some subtle differences.

在大多数情况下,两者将产生相同的结果,但我们将看看一些微妙的差异。

2. A Simple List

2.一个简单的清单

First, let’s create a list to iterate over:

首先,让我们创建一个列表来进行迭代。

List<String> list = Arrays.asList("A", "B", "C", "D");

The most straightforward way is using the enhanced for-loop:

最直接的方法是使用增强的for-loop。

for(String s : list) {
    //do something with s
}

If we want to use functional-style Java, we can also use forEach().

如果我们想使用函数式的Java,我们也可以使用forEach()

We can do so directly on the collection:

我们可以直接在收集上这样做。

Consumer<String> consumer = s -> { System.out::println }; 
list.forEach(consumer);

Or we can call forEach() on the collection’s stream:

或者我们可以在集合的流上调用forEach()

list.stream().forEach(consumer);

Both versions will iterate over the list and print all elements:

这两个版本都会对列表进行迭代并打印所有的元素。

ABCD ABCD

In this simple case, it doesn’t make a difference which forEach() we use.

在这个简单的案例中,我们使用哪个forEach()并没有什么区别。

3. Execution Order

3.执行令

Collection.forEach() uses the collection’s iterator (if one is specified), so the processing order of the items is defined. In contrast, the processing order of Collection.stream().forEach() is undefined.

Collection.forEach()使用集合的迭代器(如果指定了一个迭代器),所以项目的处理顺序是确定的。相反,Collection.stream().forEach()的处理顺序是未定义的。

In most cases, it doesn’t make a difference which of the two we choose.

在大多数情况下,我们选择两者中的哪一个并没有什么区别。

3.1. Parallel Streams

3.1.平行流

Parallel streams allow us to execute the stream in multiple threads, and in such situations, the execution order is undefined. Java only requires all threads to finish before any terminal operation, such as Collectors.toList(), is called.

并行流允许我们在多个线程中执行流,在这种情况下,执行顺序是不确定的。Java只要求在调用任何终端操作(如Collectors.toList())之前,所有线程都要完成。

Let’s look at an example where we first call forEach() directly on the collection, and second, on a parallel stream:

让我们看一个例子,我们首先在集合上直接调用forEach(),然后在一个并行流上调用。

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);

If we run the code several times, we see that list.forEach() processes the items in insertion order, while list.parallelStream().forEach() produces a different result at each run.

如果我们多次运行代码,我们看到list.forEach()按照插入顺序处理项目,而list.parallelStream().forEach()在每次运行时产生不同的结果。

Here is one possible output:

这里是一个可能的输出。

ABCD CDBA

And this is another:

而这是另一个。

ABCD DBCA

3.2. Custom Iterators

3.2.自定义迭代器

Let’s define a list with a custom iterator to iterate over the collection in reversed order:

让我们定义一个带有自定义迭代器的列表,以反转的顺序迭代该集合。

class ReverseList extends ArrayList<String> {

    @Override
    public Iterator<String> iterator() {

        int startIndex = this.size() - 1;
        List<String> list = this;

        Iterator<String> it = new Iterator<String>() {

            private int currentIndex = startIndex;

            @Override
            public boolean hasNext() {
                return currentIndex >= 0;
            }

            @Override
            public String next() {
                String next = list.get(currentIndex);
                currentIndex--;
                return next;
             }

             @Override
             public void remove() {
                 throw new UnsupportedOperationException();
             }
         };
         return it;
    }
}

Then we’ll iterate over the list again with forEach() directly on the collection and then on the stream:

然后我们将用forEach()直接对集合进行迭代,然后对流进行迭代。

List<String> myList = new ReverseList();
myList.addAll(list);

myList.forEach(System.out::print); 
System.out.print(" "); 
myList.stream().forEach(System.out::print);

And we get different results:

而我们得到了不同的结果。

DCBA ABCD

The reason for the different results is that forEach() used directly on the list uses the custom iterator, while stream().forEach() simply takes elements one by one from the list, ignoring the iterator.

产生不同结果的原因是,直接用于列表的forEach()使用了自定义的迭代器,stream().forEach()只是从列表中一个一个地获取元素,忽略了迭代器。

4. Modification of the Collection

4.收藏品的修改

Many collections (e.g. ArrayList or HashSet) shouldn’t be structurally modified while iterating over them. If an element is removed or added during an iteration, we’ll get a ConcurrentModification exception.

许多集合(例如ArrayListHashSet)在迭代时不应该被结构化地修改。如果一个元素在迭代过程中被移除或添加,我们会得到一个ConcurrentModification异常。

Furthermore, collections are designed to fail fast, which means that the exception is thrown as soon as there’s a modification.

此外,集合被设计为快速失败,这意味着一旦有修改,就会抛出异常。

Similarly, we’ll get a ConcurrentModification exception when we add or remove an element during the execution of the stream pipeline. However, the exception will be thrown later.

类似地,当我们在流管道的执行过程中添加或删除一个元素时,我们将得到一个ConcurrentModification异常。然而,该异常将在之后被抛出。

Another subtle difference between the two forEach() methods is that Java explicitly allows modifying elements using the iterator. Streams, in contrast, should be non-interfering.

这两种forEach()方法之间的另一个微妙区别是,Java明确地允许使用迭代器修改元素。相比之下,流应该是不受干扰的。

Let’s look at removing and modifying elements in more detail.

让我们更详细地了解一下删除和修改元素的情况。

4.1. Removing an Element

4.1.移除一个元素

Let’s define an operation that removes the last element (“D”) of our list:

让我们定义一个操作,删除我们列表中的最后一个元素(”D”)。

Consumer<String> removeElement = s -> {
    System.out.println(s + " " + list.size());
    if (s != null && s.equals("A")) {
        list.remove("D");
    }
};

When we iterate over the list, the last element is removed after the first element (“A”) is printed:

当我们在列表上迭代时,第一个元素(”A”)被打印出来后,最后一个元素被删除。

list.forEach(removeElement);

Since forEach() is fail-fast, we stop iterating and see an exception before the next element is processed:

由于forEach()是快速失败的,我们在处理下一个元素之前停止迭代并看到一个异常

A 4
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList.forEach(ArrayList.java:1252)
	at ReverseList.main(ReverseList.java:1)

Let’s see what happens if we use stream().forEach() instead:

让我们看看如果我们用stream().forEach()代替会发生什么。

list.stream().forEach(removeElement);

Here we continue iterating over the whole list before we see an exception:

在这里,我们在看到一个异常之前继续迭代整个列表

A 4
B 3
C 3
null 3
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1380)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at ReverseList.main(ReverseList.java:1)

However, Java does not guarantee that a ConcurrentModificationException is thrown at all. That means we should never write a program that depends on this exception.

然而,Java并不保证完全不会抛出ConcurrentModificationException这意味着我们永远都不应该写一个依赖这种异常的程序。

4.2. Changing Elements

4.2.变化的元素

We can change an element while iterating over a list:

我们可以在迭代列表的同时改变一个元素。

list.forEach(e -> {
    list.set(3, "E");
});

But while there is no problem with doing this using either Collection.forEach() or stream().forEach(), Java requires an operation on a stream to be non-interfering. This means that elements shouldn’t be modified during the execution of the stream pipeline.

但是,虽然使用Collection.forEach()stream().forEach()进行操作没有问题,但Java要求对流的操作必须是不干涉的。这意味着在执行流管道的过程中,元素不应该被修改。

The reason behind this is that the stream should facilitate parallel execution. Here, modifying elements of a stream could lead to unexpected behavior.

这背后的原因是,流应该有利于并行执行。在这里,修改流的元素可能会导致意外的行为。

5. Conclusion

5.总结

In this article, we saw some examples that show the subtle differences between Collection.forEach() and Collection.stream().forEach().

在这篇文章中,我们看到一些例子显示了Collection.forEach()Collection.stream().forEach()之间的微妙差别。

It’s important to note that all the examples shown above are trivial and meant to only compare the two ways of iterating over a collection. We shouldn’t write code whose correctness relies on the shown behavior.

需要注意的是,上面显示的所有例子都是微不足道的,只是为了比较在一个集合上迭代的两种方式。我们不应该写那些正确性依赖于所示行为的代码。

If we don’t require a stream but only want to iterate over a collection, the first choice should be using forEach() directly on the collection.

如果我们不需要一个流,而只想在一个集合上进行迭代,首选应该是直接在集合上使用forEach()

The source code for the examples in this article is available over on GitHub.

本文例子的源代码可在GitHub上获得