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

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

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

Java 9 Early Access Javadoc中提供了相同的文本。

第一句(“明确不确定”)表明(但没有明确说明)此方法不会保留遭遇顺序。 但下一句明确表示顺序未被保留的句子以“For parallel stream pipeline”为条件,如果不管并行性如何应用该句子,则不需要该条件。 这让我不确定forEach是否会保留顺序流的顺序。

这个答案指出了流库实现调用.sequential().forEach(downstream) 。 这表明forEach旨在保留顺序流的顺序,但也可能只是库中的错误。

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

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

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

如果forEach不保留遭遇顺序,则会引入错误。 在我报告针对NetBeans的错误之前,我想知道库实际上保证了什么,并由源备份。

我正在寻找权威人士答案 。 这可能是图书馆实施中的明确评论,关于Java开发邮件列表的讨论(谷歌没有找到任何东西,但我可能不知道这些神奇的词汇),或者是图书馆设计师的声明(其中我知道两个, Brian Goetz和Stuart Marks ,积极参与Stack Overflow)。 (请不要回答“只是使用forEachOrdered” – 我已经做了,但我想知道代码是否错误。)

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

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

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

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

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