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

时间:2014-04-22 11:59:24

标签: java collections java-8 java-stream

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

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

4 个答案:

答案 0 :(得分:242)

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

一个问题是订购。对于Stream.forEach,订单为未定义。它不太可能出现在顺序流中,它仍然在Stream.forEach的规范中以某种任意顺序执行。这在并行流中经常发生。相比之下,Iterable.forEach总是以Iterable的迭代顺序执行,如果指定了一个。{/ p>

另一个问题是副作用。 Stream.forEach中指定的操作必须非干扰。 (请参阅java.util.stream package doc。)Iterable.forEach可能限制较少。对于java.util中的集合,Iterable.forEach通常会使用该集合的Iterator,其中大多数设计为fail-fast,并且会抛出ConcurrentModificationException如果在迭代期间对集合进行结构修改。但是,在迭代期间允许不具有结构的修改。例如,ArrayList class documentation表示"仅设置元素的值不是结构修改。"因此,允许ArrayList.forEach的操作在基础ArrayList中设置值而不会出现问题。

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

如果Iterable.forEach在同步集合上迭代,则还可以看到另一个区别。在这样的集合上,Iterable.forEach takes the collection's lock一次并将其保存在对action方法的所有调用中。 Stream.forEach调用使用了集合的分裂器,它没有锁定,并且依赖于主流的非干扰规则。在迭代期间可以修改支持流的集合,如果是,则可能导致ConcurrentModificationException或不一致的行为。

答案 1 :(得分:15)

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

您应该在目标系统下重复此测试,因为这是特定于实现的(full source code)。

我在快速的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;
    });
}

以下是我的时间:毫秒/功能/列表中的条目数。 每次运行都是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

如果您重复实验,我发布了full source code。请编辑此答案,并使用已测试系统的表示法为您添加结果。

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

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

答案 2 :(得分:8)

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

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

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

答案 3 :(得分:1)

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