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

Vla*_*adS 271 java collections java-8 java-stream

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

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

Stu*_*rks 268

对于诸如图示的简单情况,它们大多是相同的.但是,有许多微妙的差异可能很重要.

一个问题是订购.有Stream.forEach,订单未定义.顺序流不太可能发生,但仍然在规范内Stream.forEach以某种任意顺序执行.这在并行流中经常发生.相反,如果指定了一个,Iterable.forEach则始终以迭代顺序执行Iterable.

另一个问题是副作用.指定的操作Stream.forEach必须是非干扰的.(请参阅java.util.stream包文档.)Iterable.forEach可能具有较少的限制.对于集合中java.util,Iterable.forEach通常会使用该集合Iterator,其中大部分都设计为快速失败,ConcurrentModificationException如果在迭代期间对集合进行结构修改,则会抛出该集合.但是,修改是不是结构性迭代过程中允许的.例如,ArrayList类文档说"仅设置元素的值不是结构修改".因此,ArrayList.forEach允许操作for 在底层设置值ArrayList而没有问题.

并发集合再次不同.它们不是快速失败,而是设计为弱一致的.完整的定义是在那个链接上.简而言之,请考虑一下ConcurrentLinkedDeque.传递给它的操作forEach方法允许修改底层双端队列,即使结构上,并且ConcurrentModificationException永远不会抛出.但是,在此迭代中发生的修改可能会也可能不会显示.(因此"弱"的一致性.)

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

  • 例如,参见http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/jdk8-b132/src/share/classes/java/util/Collections.java#l2121. (9认同)

Ang*_*chs 25

这个答案关注于循环的各种实现的性能.它与称为VERY OFTEN的循环(如数百万次调用)仅略微相关.在大多数情况下,循环的内容将是迄今为止最昂贵的元素.对于经常循环的情况,这可能仍然很有意义.

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

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

我编写了一个测试,它使用不同大小integers(10 ^ 0 - > 10 ^ 5个条目)的代码在List上循环10 ^ 6次.

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

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

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;
    });
}
Run Code Online (Sandbox Code Playgroud)

这是我的时间:毫秒/功能/列表中的条目数.每次运行是10 ^ 6个循环.

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

如果您重复实验,我会发布完整的源代码.请编辑此答案,并使用已测试系统的表示法为您添加结果.

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

(MacBook Pro,2.5 GHz Intel Core i7,16 GB,macOS 10.12.6)

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

ski*_*iwi 8

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

在内部,stream()由于对象创建,版本有更多的开销,但是查看运行时间,它既没有开销.

两种实现最终都会迭代collection内容一次,并且迭代期间打印出元素.

  • 这个答案似乎与在Oracle Corporation开发Java核心库的绅士所写的出色答案相矛盾. (29认同)

cpa*_*tel 6

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

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

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);
Run Code Online (Sandbox Code Playgroud)

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

ABCD CDBA
Run Code Online (Sandbox Code Playgroud)

另一种是:

ABCD DBCA
Run Code Online (Sandbox Code Playgroud)

  • 请不要只是从这里复制粘贴:https://www.baeldung.com/java-collection-stream-foreach (5认同)