Stream.forEach 是否尊重顺序流的相遇顺序? [英] Does Stream.forEach respect the encounter order of sequential streams?

查看:20
本文介绍了Stream.forEach 是否尊重顺序流的相遇顺序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Stream.forEach 说(强调我的):

<块引用>

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

相同的文本出现在 Java 9 Early Access Javadoc.

第一句话(explicitly nondeterministic")暗示(但没有明确说明)这种方法不会保留遇到顺序.但是下一句,明确表示不保留顺序,以对于并行流管道"为条件,如果无论并行性如何都应用该句子,则该条件将是不必要的.这让我不确定 forEach 是否保留顺序流的遇到顺序.

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

为了安全起见,我通过使用 forEachOrdered 在自己的代码中避免了这种歧义,但今天我发现 NetBeans IDE 的使用函数式操作"编辑器提示将转换

for (Foo foo : 集合)foo.bar();

进入

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

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

我正在寻找来自权威来源的答案.这可能是库实现中的明确评论、Java 开发邮件列表上的讨论(谷歌没有为我找到任何东西,但也许我不知道魔术词),或者是库设计者的声明(其中我知道两个,Brian GoetzStuart Marks,在 Stack Overflow 上很活跃).(请不要回答只使用 forEachOrdered"——我已经这样做了,但我想知道没有错误的代码.)

解决方案

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

当规范说不保留属性 X"时,并不意味着属性 X 可能永远不会被观察到;这意味着实施没有义务保留它.你声称的遭遇顺序永远不会被保留的暗示只是一个错误的结论.(HashSet 不保证迭代其元素会保留它们插入的顺序,但这并不意味着这不会意外发生——你只是不能指望它.)>

同样,您暗示建议 forEach 旨在保留顺序流的顺序",因为您看到在某些情况下这样做的实现同样不正确.

在这两种情况下,您似乎对规范为 forEach 提供了很大的自由这一事实感到不舒服.具体来说,它可以自由地不保留顺序流的遇到顺序,即使这是当前的实现所做的,而且很难想象一个实现会不按顺序处理顺序源.但这就是规范所说的,这就是它的意图.

也就是说,有关并行流的评论的措辞可能令人困惑,因为仍有可能对其进行误解.在这里明确提出平行案例的目的是为了教学;规范仍然非常清楚,完全删除了这句话.但是,对于不了解并行性的读者来说,几乎不可能假设 forEach 会保留遇到顺序,因此添加这句话是为了帮助阐明动机.但是,正如您所指出的,对连续案例进行特殊处理的愿望仍然如此强烈,因此进一步澄清将大有裨益.

The Javadoc for Stream.forEach says (emphasis mine):

The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism. For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization.

The same text is present in the Java 9 Early Access Javadoc.

The first sentence ("explicitly nondeterministic") suggests (but doesn't explicitly say) that encounter order is not preserved by this method. But the next sentence, that explicitly says order is not preserved, is conditioned on "For parallel stream pipelines", and that condition would be unnecessary if the sentence applied regardless of parallelism. That leaves me unsure whether forEach preserves encounter order for sequential streams.

This answer points out a spot where the streams library implementation calls .sequential().forEach(downstream). That suggests forEach is intended to preserve order for sequential streams, but could also just be a bug in the library.

I've sidestepped this ambiguity in my own code by using forEachOrdered to be on the safe side, but today I discovered that NetBeans IDE's "use functional operations" editor hint will convert

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

into

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

which introduces a bug if forEach does not preserve encounter order. Before I report a bug against NetBeans, I want to know what the library actually guarantees, backed up by a source.

I'm looking for an answer drawing from authoritative sources. That could be an explicit comment in the library implementation, discussion on the Java development mailing lists (Google didn't find anything for me but maybe I don't know the magic words), or a statement from the library designers (of which I know two, Brian Goetz and Stuart Marks, are active on Stack Overflow). (Please do not answer with "just use forEachOrdered instead" -- I already do, but I want to know if code that doesn't is wrong.)

解决方案

Specifications exist to describe the minimal guarantees a caller can depend on, not to describe what the implementation does. This gap is crucial, as it allows the implementation flexibility to evolve. (Specification is declarative; implementation is imperative.) Overspecification is just as bad as underspecification.

When a specification says "does not preserve property X", it does not mean that the property X may never be observed; it means the implementation is not obligated to preserve it. Your claimed implication that encounter order is never preserved is simply a wrong conclusion. (HashSet doesn't promise that iterating its elements preserves the order they were inserted, but that doesn't mean this can't accidentally happen -- you just can't count on it.)

Similarly, your implication of "that suggests forEach is intended to preserve order for sequential streams" because you saw an implementation that does so in some case is equally incorrect.

In both cases, it seems like you're just uncomfortable with the fact that the specification gives forEach a great deal of freedom. Specifically, it has the freedom to not preserve encounter order for sequential streams, even though that's what the implementation currently does, and further that it's kind of hard to imagine an implementation going out of its way to process sequential sources out of order. But that's what the spec says, and that's what it was intended to say.

That said, the wording of the comment about parallel streams is potentially confusing, because it is still possible to misinterpret it. The intent of calling out the parallel case explicitly here was pedagogical; the spec is still perfectly clear with that sentence removed entirely. However, to a reader who is unaware of parallelism, it would be almost impossible to not assume that forEach would preserve encounter order, so this sentence was added to help clarify the motivation. But, as you point out, the desire to treat the sequential case specially is still so powerful that it would be beneficial to clarify further.

这篇关于Stream.forEach 是否尊重顺序流的相遇顺序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆