Stream.forEach是否遵循顺序流的遭遇顺序?

时间:2015-12-13 02:18:24

标签: java java-8 java-stream

Stream.forEach的Javadoc说(强调我的):

  

此操作的行为明确是不确定的。 对于并行流管道,此操作不保证遵守流的遭遇顺序,因为这样做会牺牲并行性的好处。对于任何给定元素,可以在任何时间以及库选择的任何线程中执行该动作。如果操作访问共享状态,则它负责提供所需的同步。

Java 9 Early Access Javadoc中存在相同的文字。

第一句话("明确不确定")建议(但并未明确说明)此方法不会保留遭遇顺序。但下一句明确表示顺序未被保留的句子取决于"对于并行流管道",如果不管并行性如何应用该句子,则该条件是不必要的。这让我不确定forEach是否会保留顺序流的顺序。

This answer指出了流库实现calls .sequential().forEach(downstream)的位置。这表明forEach旨在保留顺序流的顺序,但也可能只是库中的错误。

我通过使用forEachOrdered安全地回避了我自己的代码中的这种歧义,但今天我发现NetBeans IDE"使用功能操作"编辑器提示将转换

for (Foo foo : collection)
    foo.bar();

collection.stream().forEach((foo) -> {
    foo.bar();
});

如果forEach不保留遭遇顺序,则会引入错误。在我报告a bug against NetBeans之前,我想知道图书馆实际上保证了什么,并由来源支持。

我正在寻找权威来源的答案。这可能是图书馆实施中的一个明确的评论,关于Java开发邮件列表的讨论(谷歌没有为我找到任何东西,但也许我不知道这些神奇的词汇),或者来自图书馆设计师的陈述(其中我知道两个,Brian GoetzStuart Marks,在Stack Overflow上有效)。 (请不要用&#34回答;只需使用forEachOrdered代替" - 我已经这样做了,但我想知道代码是否错误。)

1 个答案:

答案 0 :(得分:17)

存在一些规范来描述调用者可以依赖的最小保证,而不是描述实现的功能。这种差距至关重要,因为它允许实施灵活性发展。 (规范是声明性的;实施是必要的。)过度规范与指定一样糟糕。

当规范说“不保留属性X”时,并不意味着可能永远不会观察到属性X;这意味着实施没有义务保留它。您所声称的从未遵守订单的含义只是一个错误的结论。 (HashSet并不保证迭代其元素会保留它们的插入顺序,但这并不意味着这不会意外发生 - 你只是不能指望它。)

同样,你暗示“这表示forEach旨在保留顺序流的顺序”,因为你看到在某些情况下这样做的实现同样不正确。

在这两种情况下,您似乎对规范赋予forEach大量自由这一事实感到不安。具体来说,它可以自由地保留顺序流的遭遇顺序,即使这是实现当前所做的事情,而且很难想象一个实现不按顺序处理顺序源的方式。但这就是规范所说的,而这就是它的意图。

尽管如此,关于并行流的评论的措辞可能会令人困惑,因为它仍然可能会误解它。这里明确地提出并行案例的意图是教学法;该规则仍然完全清楚,完全删除了该句子。然而,对于不知道并行性的读者来说,假设forEach会保留遭遇顺序几乎是不可能的,所以添加这句话是为了帮助澄清动机。但是,正如你所指出的那样,特别对待连续案例的愿望仍然是如此强大,以至于进一步澄清是有益的。