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


286

我了解使用.stream(),我可以使用类似的链操作.filter()或使用并行流。但是,如果我需要执行小的操作(例如,打印列表的元素),它们之间有什么区别?

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);

Answers:


287

对于诸如所示的简单情况,它们基本上是相同的。但是,有许多细微的差别可能很重要。

一个问题是订购。使用Stream.forEach,顺序不确定。顺序流不太可能发生,但是,它在规范中可以Stream.forEach以任意顺序执行。这确实在并行流中经常发生。相反,如果指定Iterable.forEachIterable,则总是按的迭代顺序执行。

另一个问题是副作用。Stream.forEach必须指定中的动作为非干扰动作。(请参阅java.util.stream软件包doc。)Iterable.forEach可能会有更少的限制。对于中的集合java.utilIterable.forEach通常将使用该集合的Iterator,其中大多数被设计为快速失败的,并且ConcurrentModificationException如果在迭代过程中对集合进行结构修改,则将抛出该集合。但是,在迭代过程允许进行非结构化的修改。例如,ArrayList类文档说“仅设置元素的值不是结构修改”。因此,针对ArrayList.forEach允许在底层中设置值ArrayList而不会出现问题。

并发集合又一次不同。它们不是快速失败,而是设计为弱一致性。完整定义在该链接上。不过,请简要考虑一下ConcurrentLinkedDeque。传递给其forEach方法的操作允许修改底层双端队列,甚至在结构上可以修改,并且ConcurrentModificationException永远不会被抛出。但是,发生的修改在此迭代中可能可见,也可能不可见。(因此保持“弱”一致性。)

如果Iterable.forEach在同步的集合上进行迭代,则可以看到另一个差异。在这样的集合上,Iterable.forEach 获取一次该集合的锁,并在对action方法的所有调用中保持该。该Stream.forEach调用使用集合的分隔符,该分隔符不会锁定,并且依赖于流行的不干扰规则。支持该流的集合可以在迭代期间进行修改,如果是,则ConcurrentModificationException可能导致行为不一致或行为不一致。


Iterable.forEach takes the collection's lock。这些信息来自哪里?我在JDK源代码中找不到这种行为。
turbanoff 2015年


@Stuart,您能详细说说不干扰吗?Stream.forEach()也将抛出ConcurrentModificationException(至少对我而言)。
yuranos

1
@ yuranos87之类的许多集合ArrayList都对并发修改进行了相当严格的检查,因此经常会抛出ConcurrentModificationException。但这并不能保证,特别是对于并行流。您可能会得到意想不到的答案,而不是CME。还应考虑对流源进行非结构性修改。对于并行流,您不知道哪个线程将处理特定元素,也不知道在修改该元素时是否已对其进行处理。这将设置一个竞争条件,在此条件下,您每次运行可能会得到不同的结果,而永远不会获得CME。
斯图尔特(Stuart Marks)

29

该答案与循环的各种实现的性能有关。它与被称为“非常频繁”的循环(如数百万个调用)的边际相关。在大多数情况下,循环的内容将是迄今为止最昂贵的元素。对于确实经常循环的情况,这可能仍然很有趣。

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

我在快速的Linux机器上运行openjdk版本1.8.0_111。

我编写了一个测试,使用此代码在列表上循环10 ^ 6次,该代码的大小各不相同integers(10 ^ 0-> 10 ^ 5个条目)。

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

但是,即使在最坏的情况下,表现最差的人也要花10秒循环10 ^ 5个条目10 ^ 6次,因此实际上在所有情况下其他考虑因素都更为重要。

public int outside = 0;

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

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

private void iteratorForEach(List<Integer> integers) {
    integers.forEach((ii) -> {
        outside = ii*ii;
    });
}
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 Intel Core i7、16 GB,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,8 GB,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

Java 11 Hotspot VM-3.4GHz Intel Xeon,8 GB,Windows 10 Pro
(与上述相同的计算机,不同的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

Java 11 OpenJ9 VM- 3.4 GHz Intel Xeon,8 GB,Windows 10 Pro
(与上述相同的计算机和JDK版本,不同的VM)

                            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,64 GB,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版本,不同的VM)

                            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

您选择的VM实施也会有所不同Hotspot / OpenJ9 / etc。


3
这是一个很好的答案,谢谢!但是从第一眼(以及第二眼)来看,还不清楚哪种方法对应于什么实验。
torina

我觉得这个答案需要代码测试更多的投票:)。
科里

测试示例+1
Centos,

8

您提到的两者之间没有任何区别,至少从概念上讲,这Collection.forEach()只是一个简写。

在内部,stream()由于创建对象,该版本的开销有所增加,但是从运行时间来看,该版本都没有开销。

两种实现都最终对collection内容进行一次迭代,并迭代过程中打印出元素。


您提到的对象创建开销是指Stream创建对象还是单个对象?AFAIK,a Stream不重复元素。
Raffi Khatchadourian

30
这个答案似乎与Oracle公司开发Java核心库的绅士写的出色答案相矛盾。
达伍德·伊本·卡里姆

0

Collection.forEach()使用集合的迭代器(如果已指定)。这意味着已定义项目的处理顺序。相反,Collection.stream()。forEach()的处理顺序是不确定的。

在大多数情况下,我们选择两者中的哪一个没有区别。并行流允许我们在多个线程中执行流,在这种情况下,执行顺序是不确定的。Java仅要求在调用任何终端操作(例如Collectors.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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.