【问题标题】:What is the difference between Java8 container `for each` and Stream `for each` [duplicate]Java 8容器`foreach`和Stream`for each`有什么区别[重复]
【发布时间】:2018-11-12 17:36:03
【问题描述】:

我知道我们可以使用List.foreach 进行遍历,也可以使用List.stream.foreach进行遍历。我不明白在 Java8 中遍历哪个更好。

【问题讨论】:

  • List.foreach 会在没有流媒体开销的情况下做到这一点。
  • 当您只想迭代时最好使用List.forEach,因为它不会创建不必要的Stream。
  • @Andreas 这是今天,在Java8中,List.foreach也是在stream中实现的吗?
  • @Slaw 不知道哪种性能更好
  • 如果您使用List.stream().forEach() 做几乎任何重要的事情......那么您可能做错了。

标签: java java-8 iteration


【解决方案1】:

forEach(Consumer) 方法在 Iterable 接口中声明,Collection 和 List 扩展了该接口。 forEach(Consumer) 的默认实现是:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

如您所见,默认实现只是在 for-each 循环中调用 action。 for-each 循环只是语法糖:

for (Iterator<?> iterator = iterable.iterator(); iterator.hasNext(); ) {
    Object element = iterator.next();
    // Do what you need to with element
} 

除非您在 for-each 循环中无权访问 Iterator。

Iterable 的特定实现可能会改变它实际迭代其元素的方式(它可能使用也可能不使用 Iterator),但它几乎总是归结为一些 for 或 @ 987654334@ 循环。我说“几乎总是”是因为可能涉及某种类型的递归或链接。

现在,当您只是尝试按顺序迭代List 时,使用List.stream().forEach(Consumer) 会创建一个不必要的Stream 对象。如果您确实需要以管道方式处理元素集合(例如映射、过滤、更多映射等),则应仅使用流 API。

因此,对于简单的迭代,使用List.stream().forEach(Consumer) 在几乎所有情况下都比简单的List.forEach(Consumer) 调用性能要差。性能提升很可能可以忽略不计,但“优化”并不过分是一个足够简单的修复;特别是如果您一开始就没有犯“错误”。如果不需要,请勿创建对象。

不过,最好使用 for-each 循环而不是 forEach(Consumer)。它比功能更强大的对应物更容易阅读。


编辑

正如 Holger 在 cmets 中提到的,Stream.forEach(Consumer) 与 Iterable.forEach(Consumer) 有很大的不同:它不保证元素的相遇顺序。虽然Iterable.forEach(Consumer)的迭代顺序也没有为Iterable接口定义,但可以通过扩展接口(如List)来定义。但是,当使用Stream 时,无论Stream 的来源如何,都不能保证顺序。

如果您希望在使用Stream 时保证订单,您必须使用Stream.forEachOrdered(Consumer)。

【讨论】:

  • 这遗漏了最重要的一点,即不同的语义。 Stream.forEach 将以未指定的顺序 进行迭代,这可能恰好与大多数当前实现中的遭遇顺序相匹配,但不能保证。另一个是同步集合上的forEach 可能会在集合的锁定下执行整个迭代,而Stream.forEach 不会发生这种情况。
  • @Holger 在并行流中,您提到的始终是正确的。对于顺序流,forEach 的排序取决于Spliterator 的特征(我假设)。例如,所有List 实现都将有一个Spliterator,它具有Spliterator.ORDERED 特性(如果它们遵循合同)。但是Set 的实现可能不会。在这方面,为Iterable.forEach 定义的迭代顺序(或遇到顺序)并不比为Stream.forEach 定义的更好。同样,这是针对此答案所关注的顺序流。
  • 不,Stream.forEach 是“明确不确定的”。无论来源具有什么特征,都不需要遵守任何顺序。不要将当前实现的行为与规范的保证混淆。如果您需要订购,请使用forEachOrdered 或留下Collection.forEach。
  • @Holger 好吧,我承认你的观点。但是,我会说,我相信不遵循源代码迭代顺序的实现将不得不做额外的工作才能做到这一点。它必须做一些事情,比如获取第一个元素,获取第二个元素,使用第二个元素,然后使用第一个元素。同样,当我这样说时,我只关注非并行流。编辑了我的答案。
  • 是的,很难想象一个顺序执行场景可以利用未定义的迭代顺序来获得优势,而目前,实现只是委托给Spliterator.forEachRemaining,它甚至不知道它是否正在服务forEach 或 forEachOrdered。但即使它仍然是纯粹的概念事物; stream().forEach(action) 向未来的读者建议 action 是顺序无关的,所以如果不是这样就不好了..
【解决方案2】:

请注意,虽然List.foreach 看起来与List.stream.foreach 相似,但它实际上并没有使用流式传输,因此它不会产生流式传输的开销。

为了比较执行代码的复杂性,下面我将展示这两种结构如何工作的简略版本,为了清晰起见,通过删除验证逻辑进行了简化。

`List.foreach`

例如在ArrayList 中,这实现为:

public void forEach(Consumer<? super E> action) {
    for (int i = 0; i < size(); i++)
        action.accept(get(i));
}

就是这样。就是这么简单。

`List.stream.foreach`

这需要多种方法:

// In Collection
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

// In ArrayList
public Spliterator<E> spliterator() {
    return new ArrayListSpliterator(0, -1, 0);
}

// In StreamSupport
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
    return new ReferencePipeline.Head<>(spliterator,
                                        StreamOpFlag.fromCharacteristics(spliterator),
                                        parallel);
}

// In ReferencePipeline
public void forEach(Consumer<? super P_OUT> action) {
    evaluate(ForEachOps.makeRef(action, false));
}

// In ForEachOps
public static <T> TerminalOp<T, Void> makeRef(Consumer<? super T> action,
                                              boolean ordered) {
    return new ForEachOp.OfRef<>(action, ordered);
}

// many method calls eventually leading to

// In ArrayListSpliterator
public void forEachRemaining(Consumer<? super E> action) {
    for (int i = 0; i < size(); i++)
        action.accept(get(i));
}

如您所见,它运行了更多代码,并创建了至少 3 个以上的对象。

【讨论】:

    猜你喜欢
    • 2015-05-19
    • 2016-06-11
    • 2013-03-28
    • 2018-09-17
    • 1970-01-01
    • 1970-01-01
    • 2012-06-11
    • 2016-07-20
    相关资源
    最近更新 更多