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 Goetz和Stuart Marks,在Stack Overflow上有效)。 (请不要用&#34回答;只需使用forEachOrdered代替" - 我已经这样做了,但我想知道代码是否错误。)
答案 0 :(得分:17)
存在一些规范来描述调用者可以依赖的最小保证,而不是描述实现的功能。这种差距至关重要,因为它允许实施灵活性发展。 (规范是声明性的;实施是必要的。)过度规范与指定一样糟糕。
当规范说“不保留属性X”时,并不意味着可能永远不会观察到属性X;这意味着实施没有义务保留它。您所声称的从未遵守订单的含义只是一个错误的结论。 (HashSet
并不保证迭代其元素会保留它们的插入顺序,但这并不意味着这不会意外发生 - 你只是不能指望它。)
同样,你暗示“这表示forEach旨在保留顺序流的顺序”,因为你看到在某些情况下这样做的实现同样不正确。
在这两种情况下,您似乎对规范赋予forEach
大量自由这一事实感到不安。具体来说,它可以自由地保留顺序流的遭遇顺序,即使这是实现当前所做的事情,而且很难想象一个实现不按顺序处理顺序源的方式。但这就是规范所说的,而这就是它的意图。
尽管如此,关于并行流的评论的措辞可能会令人困惑,因为它仍然可能会误解它。这里明确地提出并行案例的意图是教学法;该规则仍然完全清楚,完全删除了该句子。然而,对于不知道并行性的读者来说,不假设forEach
会保留遭遇顺序几乎是不可能的,所以添加这句话是为了帮助澄清动机。但是,正如你所指出的那样,特别对待连续案例的愿望仍然是如此强大,以至于进一步澄清是有益的。