Stream.forEach是否遵守顺序流的相遇顺序?

29

Stream.forEach的Javadoc(重点是我的)说:

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

相同的文本也出现在Java 9 Early Access Javadoc中。

第一句话(“明确的不确定性”)表明(但并没有明确说明)该方法不保留遇到顺序。 但是下一句话明确说明顺序不被保留,其条件是“对于并行流水线”,如果该句适用于无论是否并行,那么该条件是不必要的。 这使我不确定forEach是否为顺序流保留了遇到顺序。

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

我在自己的代码中通过使用forEachOrdered来避免这种歧义,但今天我发现NetBeans IDE的“使用函数操作”编辑提示将转换

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

转换为

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

如果forEach不保留遇到的顺序,则会引入错误。在我向NetBeans报告错误之前,我想知道该库实际上保证了什么,并由来源支持。

我正在寻找从权威来源得出的答案。这可以是库实现中的明确评论,Java开发邮件列表上的讨论(Google没有为我找到任何内容,但也许我不知道魔法词),或者是库设计师的声明(其中我认识两个人,Brian GoetzStuart Marks,他们在Stack Overflow上活跃)。 (请不要回答“只需使用forEachOrdered”-我已经这样做了,但我想知道不使用它的代码是否有问题。)


5
文档说明非常清楚。forEach 忽略所有流的 "ordered" 属性,包括并行/串行、有序/无序。没有例外。即使当前流实现中没有任何代码会导致串行流的非遇到顺序,但该选项保留了未来的可能性。 - zapl
6
实际上,这个问题更深层次的探讨是:无序流特征是否对顺序流有影响(或将来可能有影响)。目前,顺序无序流总是像有序流一样被处理,但是当源或终端操作是无序时,可以进行许多优化。例如,.distinct() 使用 LinkedHashSet 保留顺序。如果终端操作是 forEachfindAny,它可以使用简单的 HashSet 以减少开销。 - Tagir Valeev
此外,即使在不需要顺序处理的情况下,人们普遍认为顺序处理是必要的。这是我们所有人都成长于其中的普遍连续性的副作用;顺序处理已经如此简单和廉价,以至于即使不需要它,也很容易不去质疑它是必需的。 - Brian Goetz
我提交了NetBeans bug 257129 - Jeffrey Bosboom
1
NetBeans真的能做到吗?为什么要调用collection.stream().forEach()而不是简单地使用collection.forEach() - shmosel
我认为forEach至少在理论上可以在顺序管道中实现一些相当激进的优化。例如,.sorted().forEach()可以省略排序。 - the8472
1个回答

20

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

当规范说“不保留属性X”时,它并不意味着属性X永远不会被观察到;它意味着实现没有义务保留它。你声称遇到顺序永远不会被保留的含义只是一个错误的结论。(HashSet不保证迭代其元素保留它们插入的顺序,但这并不意味着这种情况不会偶然发生--你只是不能指望它。)

同样,你对“这表明forEach旨在为顺序流保留顺序”的暗示,因为你看到一个在某些情况下确实这样做的实现,同样是错误的。

在这两种情况下,似乎你只是不舒服规范给了forEach很大的自由度。具体来说,它有自由度为顺序流不保留遭遇顺序,即使这是实现当前所做的事情,而且进一步说,很难想象实现会费力地处理顺序流。但这就是规范所说的,也是它的设计目的。

尽管如此,关于并行流的评论措辞可能令人困惑,因为它仍然可能被误解。在这里明确指出并行案例的意图是教育性的;如果完全删除该句子,规范仍然非常清楚。然而,对于不了解并行性的读者来说,几乎不可能不假设forEach会保留遭遇顺序,因此添加了这个句子以帮助澄清动机。但是,正如你指出的那样,特别处理顺序案例的愿望仍然非常强烈,因此有必要进一步澄清。


2
我完全支持规范允许实现自由的想法。我只是想要一个明确的声明,这样我就不必与NetBeans开发人员争论他们的错误了。(至于“从不”,我的意思是“无论流是否有序”,而不是“必须洗牌”。但是,无序的流本来就没有要保留的顺序。)除了澄清文本之外,也许添加@see forEachOrdered可以向开发人员表明有选择余地。 - Jeffrey Bosboom
2
基于经验,我认为这不会是一个澄清,我只是认为它会让不同的人群感到困惑,而不是当前版本。如果它说“不尊重”,有些人肯定会认为我们会刻意不尊重顺序,就像集合的迭代顺序被随机化一样。(为10M人使用的东西编写规范并不容易。这听起来可能很荒谬,但我们经常看到这样的事情。) - Brian Goetz
3
如果您关心遍历顺序,只需使用forEachOrdered编写能清晰捕获语义期望的代码,这样您的代码就变得清晰易懂了。而且,在顺序流上使用forEachOrdered的开销与forEach相同。根本上,我认为您真正想表达的是“请不要让我去思考像排序这样的难概念”,对于支持低仪式并行性的框架来说,我认为这不会对任何人有好处。 - Brian Goetz
1
文档的措辞不是“可能令人困惑”,而是绝对、毫无疑问地令人困惑。如果你想表达“XYZ总是适用”,那么在语句“对于并行流管道,XYZ适用”中没有其他提到XYZ的内容,当然会产生误导。这就是文档目前的写法。唯一提到“遇到顺序”的是以“对于并行流管道”开头的语句。 - Holger
1
@Holger 欢迎提交 PR 来改进措辞! - Brian Goetz
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接