我一直试图从官方Java文档中找到明确的合同,关于Java流的顺序,一旦调用终端操作,就处理元素并调用中间操作.
例如,让我们看看这些使用Java流版本和普通迭代版本的示例(两者都产生相同的结果).
例1:
List<Integer> ints = Arrays.asList(1,2,3,4,5); Function<Integer,Integer> map1 = i -> i; Predicate<Integer> f1 = i -> i > 2; public int findFirstUsingStreams(List<Integer> ints){ return ints.stream().map(map1).filter(f1).findFirst().orElse(-1); } public int findFirstUsingLoopV1(List<Integer> ints){ for (int i : ints){ int mappedI = map1.apply(i); if ( f1.test(mappedI) ) return mappedI; } return -1; } public int findFirstUsingLoopV2(List<Integer> ints){ List<Integer> 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; }
请求findFirst之后的findFirstUsingStreams方法中的Java流是否按照findFirstUsingLoopV1中描述的顺序运行map1(映射不是针对所有元素运行)或者如findFirstUsingLoopV2中所述(对所有元素运行map)?
并且这个订单会在未来的Java版本中发生变化,还是有一个官方文档可以保证我们map1调用的顺序?
例2:
Predicate<Integer> f1 = i -> i > 2; Predicate<Integer> f2 = i -> i > 3; public List<Integer> collectUsingStreams(List<Integer> ints){ return ints.stream().filter(f1).filter(f2).collect( Collectors.toList() ); } public List<Integer> collectUsingLoopV1(List<Integer> ints){ List<Integer> result = new ArrayList<>(); for (int i : ints){ if ( f1.test(i) && f2.test(i) ) result.add(i); } return result; } public List<Integer> collectUsingLoopV2(List<Integer> ints){ List<Integer> result = new ArrayList<>(); for (int i : ints){ if ( f2.test(i) && f1.test(i) ) result.add(i); } return result; }
collect之后的collectUsingStreams方法中的Java流再次按照collectUsingLoopV1中描述的顺序调用run f1和f2(f1在f2之前评估)或者如collectUsingLoopV2中所述(f1在f1之前评估)?
并且这个订单会在未来的Java版本中发生变化,还是有一个官方文档可以保证我们f1和f2调用的顺序?
编辑
感谢所有的答案和评论,但不幸的是,我仍然没有看到处理元素的顺序的良好解释.文档确实说,将为列表保留遭遇顺序,但它们不指定如何处理这些元素.例如,在findFirst的情况下,docs保证map1将首先看到1然后2但是它没有说map1不会被执行4和5.这是否意味着我们不能保证我们的处理顺序将如我们所期望的那样在Java的固定版本?可能是.
解决方法
And will that order change in future versions of Java or there is an official documentation that guarantees us the order of map1 calls?
包括package summaries(人们经常忽略那些)的javadoc是API合约.可观察但未由javadoc定义的行为通常应被视为可能在将来版本中更改的实现细节.
因此,如果在javadocs中找不到它,则无法保证.
在哪个顺序中流管道阶段被调用并且未指定交错.所指定的是在哪种情况下保留所谓的流的encounter order.假设有序流,仍允许实现执行任何将保留遭遇顺序的交错,批处理和内部重新排序.例如.排序的(比较器).filter(谓词).findFirst()可以在内部用过滤器(谓词).min(比较器)替换,这当然会显着影响谓词和比较器被调用的方式,但产生相同的结果,即使在有序的流中.
Does it mean that we cant guarantee that our order of processing will be as we expect the in fure versions of Java? Probably yes.
是的,这应该不是一个问题,因为大多数流API require callbacks to be stateless和free of side-effects,除其他外意味着他们不应该关心流管道的内部执行顺序,结果应该是相同的,模块化由无序授予的余地流.
显式要求和缺少保证为JDK开发人员提供了如何实现流的灵活性.
如果你有任何特殊情况需要考虑,你应该问一个更具体的问题,关于你想要避免的执行重新排序.
您应该始终牢记流可以是并行的,例如由第三方代码传递的实例,或者包含一个源或中间流操作,它比理论上不那么懒(当前flatMap就是这样的操作).如果有人提取和重新分割spliterator或使用Stream接口的自定义实现,则流管道也可以包含自定义行为.
因此,当特定流实现在以特定方式使用它们时可能表现出一些可预测的行为,并且对于该特定情况的未来优化可能被认为是非常不可能的,这不会推广到所有可能的流管道,因此API不能提供这样的一般保证.