Collection.stream().forEach()和Collection.forEach()之间有什么区别?

我知道,对于.stream(),我可以使用像.filter()这样的链式操作或使用并行流。但是,如果我需要执行小操作(例如,打印列表中的元素),它们之间有什么区别呢?

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
93890 次浏览

你所提到的两者之间没有区别,至少在概念上,Collection.forEach()只是一个简写。

在内部,由于对象创建,stream()版本有更多的开销,但看看运行时,它在那里也没有开销。

这两个实现最终都迭代collection内容一次,并且迭代打印出元素。

对于简单的例子,如上图所示,它们大多是相同的。然而,有一些细微的差异可能是重要的。

其中一个问题是排序。对于Stream.forEach,顺序是未定义的。它不太可能发生在顺序流中,尽管如此,它仍然在Stream.forEach的规范中以某种任意顺序执行。这在并行流中确实经常发生。相比之下,Iterable.forEach总是按照Iterable的迭代顺序执行(如果指定了Iterable)。

另一个问题是副作用。在Stream.forEach中指定的动作必须是互不干扰的。(参见Java.util.stream包文档.) Iterable.forEach的限制可能更少。对于java.util中的集合,Iterable.forEach通常会使用该集合的Iterator,其中大多数被设计为Iterable.forEach0,如果在迭代期间对集合进行了结构修改,则会抛出ConcurrentModificationException。然而,在迭代过程中不允许进行结构性Iterable.forEach1的修改。例如,Iterable.forEach2表示“仅仅设置元素的值不是结构修改”。因此,允许ArrayList.forEach的操作在底层ArrayList中设置值而不会出现问题。

并发集合又是不同的。它们不是快速失败,而是被设计为弱一致。完整的定义就在那个环节。不过,简单地考虑ConcurrentLinkedDeque。传递给它的forEach方法的动作允许修改底层deque,甚至是结构上的,并且ConcurrentModificationException永远不会抛出。然而,所发生的修改在此迭代中可能可见,也可能不可见。(因此才有了“弱”一致性。)

如果Iterable.forEach在同步集合上迭代,还可以看到另一个区别。在这样的集合中,Iterable.forEach 获取集合的锁一次,并在所有对action方法的调用中保存它。Stream.forEach调用使用集合的分离器,它不锁定,并依赖于普遍的不干涉规则。支持流的集合可以在迭代过程中被修改,如果是这样,可能会导致ConcurrentModificationException或不一致的行为。

这个答案与循环的各种实现的性能有关。它只与经常被调用的循环(比如数百万次调用)有很小的关系。在大多数情况下,循环的内容将是迄今为止最贵的元素。对于经常循环的情况,这可能仍然是有趣的。

你应该在目标系统下重复这个测试,因为这是特定于实现的,(完整源代码)。

我在一台快速的Linux机器上运行openjdk version 1.8.0_111。

我写了一个测试,在List上循环10^6次,使用不同大小的integers (10^0 ->10 ^ 5项)。

结果如下所示,最快的方法取决于列表中条目的数量。

但在最糟糕的情况下,对于最差的表现者来说,循环超过10^5个条目10^6次需要100秒,因此其他考虑因素在几乎所有情况下都更重要。

public int outside = 0;


private void iteratorForEach(List<Integer> integers) {
integers.forEach((ii) -> {
outside = ii*ii;
});
}


private void forEach(List<Integer> integers) {
for(Integer next : integers) {
outside = next * next;
}
}


private void forCounter(List<Integer> integers) {
for(int ii = 0; ii < integers.size(); ii++) {
Integer next = integers.get(ii);
outside = next*next;
}
}


private void iteratorStream(List<Integer> integers) {
integers.stream().forEach((ii) -> {
outside = ii*ii;
});
}
这是我的计时:毫秒/函数/列表中条目的数量。 每次运行是10^6个循环

                           1    10    100    1000    10000
iterator.forEach   27   116    959    8832    88958
for:each   53   171   1262   11164   111005
for with index   39   112    920    8577    89212
iterable.stream.forEach  255   324   1030    8519    88419

如果你重复实验,我张贴完整源代码。请编辑这个答案,并添加你的结果与测试系统的符号。


使用MacBook Pro, 2.5 GHz英特尔酷睿i7, 16gb, macOS 10.12.6:

                           1    10    100    1000    10000
iterator.forEach   27   106   1047    8516    88044
for:each   46   143   1182   10548   101925
for with index   49   145    887    7614    81130
iterable.stream.forEach  393   397   1108    8908    88361

Java 8 Hotspot VM - 3.4GHz Intel Xeon, 8gb, Windows 10 Pro

                            1    10    100    1000    10000
iterator.forEach   30   115    928    8384    85911
for:each   40   125   1166   10804   108006
for with index   30   120    956    8247    81116
iterable.stream.forEach  260   237   1020    8401    84883

. 0 Java 11 Hotspot VM - 3.4GHz Intel Xeon, 8gb, Windows 10 Pro
. 0 (与上面同一台机器,不同的JDK版本)

                            1    10    100    1000    10000
iterator.forEach   20   104    940    8350    88918
for:each   50   140    991    8497    89873
for with index   37   140    945    8646    90402
iterable.stream.forEach  200   270   1054    8558    87449

. 0 Java 11 OpenJ9 VM - 3.4GHz Intel Xeon, 8gb, Windows 10 Pro
. 0 (与上面相同的机器和JDK版本,不同的虚拟机)

                            1    10    100    1000    10000
iterator.forEach  211   475   3499   33631   336108
for:each  200   375   2793   27249   272590
for with index  384   467   2718   26036   261408
iterable.stream.forEach  515   714   3096   26320   262786

Java 8 Hotspot VM - 2.8GHz AMD, 64gb, Windows Server 2016

                            1    10    100    1000    10000
iterator.forEach   95   192   2076   19269   198519
for:each  157   224   2492   25466   248494
for with index  140   368   2084   22294   207092
iterable.stream.forEach  946   687   2206   21697   238457

Java 11 Hotspot VM - 2.8GHz AMD, 64 GB, Windows Server 2016
(与上面同一台机器,不同的JDK版本)

                            1    10    100    1000    10000
iterator.forEach   72   269   1972   23157   229445
for:each  192   376   2114   24389   233544
for with index  165   424   2123   20853   220356
iterable.stream.forEach  921   660   2194   23840   204817

Java 11 OpenJ9 VM - 2.8GHz AMD, 64 GB, Windows Server 2016
(与上面相同的机器和JDK版本,不同的虚拟机)

                            1    10    100    1000    10000
iterator.forEach  592   914   7232   59062   529497
for:each  477  1576  14706  129724  1190001
for with index  893   838   7265   74045   842927
iterable.stream.forEach 1359  1782  11869  104427   958584

您所选择的虚拟机实现也会产生不同的效果,例如Hotspot/OpenJ9等。

collection . foreach()使用集合的迭代器(如果指定了一个)。这意味着定义了项目的处理顺序。相反,Collection.stream(). foreach()的处理顺序是未定义的。

在大多数情况下,我们选择两者中的哪一个都没有区别。 并行流允许我们在多个线程中执行流,在这种情况下,执行顺序是未定义的。Java只要求在调用任何终端操作(如collections . tolist())之前完成所有线程。 让我们看一个例子,我们首先直接在集合上调用forEach(),然后在并行流上调用:

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);
如果我们多次运行代码,我们会看到list.forEach()按插入顺序处理项,而list.parallelStream(). foreach()每次运行都会产生不同的结果。 一个可能的输出是:

ABCD CDBA

另一个是:

ABCD DBCA

这里有很多好答案。

只是添加了Stuart(@user:1441122) Yuranos (@user:4470135)的评论,并在这里发布了一个答案。

@Yuranos,是的,我们在删除元素时得到ConcurrentModificationException,同时迭代两者。Stream.forEach()和forEach()之间的细微区别是:

Java显式地允许使用迭代器修改元素。相反,流应该是无干扰的。

对于一个列表list = [1,2,3,4] 让我们定义一个操作,删除列表的最后一个元素(4):

Consumer<Integer> removeElement = s -> {
System.out.println(s + " " + list.size());
if (s != null && s==1) {
list.remove(4);
}
};

收集forEach ()

list.forEach(removeElement);

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

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

流forEach ()

list.stream().forEach(removeElement);


1 4
2 3
3 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)