终端执行的Java流操作顺序

我一直试图从官方Java文档中找到明确的合同,关于Java流的顺序,一旦调用终端操作,就处理元素并调用中间操作。

例如,让我们看看这些使用Java流版本和普通迭代版本的示例(两者都产生相同的结果)

例1:

List ints = Arrays.asList(1, 2, 3, 4, 5); Function map1 = i -> i; Predicate f1 = i -> i > 2; public int findFirstUsingStreams(List ints){ return ints.stream().map(map1).filter(f1).findFirst().orElse(-1); } public int findFirstUsingLoopV1(List ints){ for (int i : ints){ int mappedI = map1.apply(i); if ( f1.test(mappedI) ) return mappedI; } return -1; } public int findFirstUsingLoopV2(List ints){ List mappedInts = new ArrayList( ints.size() ); for (int i : ints){ int mappedI = map1.apply(i); mappedInts.add(mappedI); } for (int mappedI : mappedInts){ if ( f1.test(mappedI) ) return mappedI; } return -1; } 

findFirstUsingStreams之后的findFirstUsingStreams方法中的Java流是否按照findFirstUsingStreams中描述的顺序运行map1map不是针对所有元素运行)或者如findFirstUsingLoopV2map是针对所有元素运行的)?

并且这个订单会在未来的Java版本中发生变化,还是有一个官方文档可以保证我们map1调用的顺序?

例2:

 Predicate f1 = i -> i > 2; Predicate f2 = i -> i > 3; public List collectUsingStreams(List ints){ return ints.stream().filter(f1).filter(f2).collect( Collectors.toList() ); } public List collectUsingLoopV1(List ints){ List result = new ArrayList(); for (int i : ints){ if ( f1.test(i) && f2.test(i) ) result.add(i); } return result; } public List collectUsingLoopV2(List ints){ List result = new ArrayList(); for (int i : ints){ if ( f2.test(i) && f1.test(i) ) result.add(i); } return result; } 

collectUsingStreams之后, collectUsingStreams方法中的Java流将按照collectUsingStreams中描述的顺序调用run f1f2f1f2之前评估)或者如collectUsingLoopV2f2f1之前评估)?

并且这个订单会在未来的Java版本中发生变化,还是有一个官方文档可以保证我们f1f2调用的顺序?

编辑

感谢所有的答案和评论,但不幸的是,我仍然没有看到处理元素的顺序的良好解释。 文档确实说,将为列表保留遭遇顺序,但它们不指定如何处理这些元素。 例如,在findFirst的情况下,docs保证map1将首先看到1然后2但是它没有说map1不会被执行4和5.这是否意味着我们不能保证我们的处理顺序将如我们所期望的那样在Java的固定版本? 可能是。

并且这个订单会在未来的Java版本中发生变化,还是有一个官方文档可以保证我们map1调用的顺序?

包括包摘要 (人们经常忽略那些)的javadoc是API合约。 可观察但未由javadoc定义的行为通常应被视为可能在将来版本中更改的实现细节。

因此,如果在javadocs中找不到它,则无法保证。

调用哪个顺序流管道阶段并且未指定交错。 指定的是在哪种情况下保留所谓的流的遭遇顺序 。 假设有序流,仍允许实现执行任何将保留遭遇顺序的交错,批处理和内部重新排序。 例如,一个sorted(comparator).filter(predicate).findFirst()可以在内部用一个filter(predicate).min(comparator)替换,这当然会显着影响谓词和比较器被调用的方式,然后产生相同的结果,即使在有序的流中。

这是否意味着我们无法保证我们的处理顺序与我们期望的Java版本一样? 可能是。

是的,这应该不是问题,因为大多数流API 要求回调是无状态的并且没有副作用 ,这除其他外意味着他们不应该关心流管道的内部执行顺序,结果应该是相同的,模数由无序流授予的余地。

显式要求和缺少保证为JDK开发人员提供了如何实现流的灵活性。

如果您有任何特殊情况需要考虑,那么您应该提出一个更具体的问题,关于您想要避免的执行重新排序。


您应该始终记住,流可以是并行的,例如,第三方代码传递的实例,或者包含源或理论上不太懒的源流或中间流操作(当前flatMap就是这样的操作)。 如果有人提取和重新分割spliterator或使用Stream 接口的自定义实现,则Stream管道还可以包含自定义行为。

因此,当特定流实现在以特定方式使用它们时可能会表现出一些可预测的行为,并且对于该特定情况的未来优化可能被认为是非常不可能的,这不会推广到所有可能的流管道,因此API无法提供这样的一般保证。