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

新手上路,请多包涵

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

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

原文由 VladS 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 450
2 个回答

对于如图所示的简单情况,它们大部分是相同的。但是,有许多细微的差异可能很重要。

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

另一个问题是副作用。 Stream.forEach 中指定的操作必须是 无干扰的。 (请参阅 java.util.stream 包文档。) Iterable.forEach 可能具有更少的限制。 For the collections in java.util , Iterable.forEach will generally use that collection’s Iterator , most of which are designed to be fail-fast and which will throw ConcurrentModificationException 如果集合在迭代过程中进行了结构修改。但是,在迭代期间允许 进行 非结构性修改。例如, ArrayList 类文档 说“仅仅设置元素的值不是结构修改”。因此,允许 ArrayList.forEach 的操作在底层 ArrayList 中设置值而没有问题。

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

如果 Iterable.forEach 正在迭代同步集合,则还有一个区别是可见的。在这样的集合上, Iterable.forEach 获取一次集合的锁,并在对操作方法的所有调用中保持它。 Stream.forEach 调用使用集合的拆分器,它不锁定,并且依赖于非干扰的普遍规则。可以在迭代期间修改支持流的集合,如果是,则可能会导致 ConcurrentModificationException 或不一致的行为。

原文由 Stuart Marks 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

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

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

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

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

但仍然在最坏的情况下,循环 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 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 热点虚拟机 - 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 热点虚拟机 - 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.4GHz 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 热点虚拟机 - 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 热点虚拟机 - 2.8GHz AMD,64GB,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 虚拟机 - 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/等产生影响。

原文由 Angelo Fuchs 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题