我一直试图从官方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;
}
findFirstUsingStreams
之后的findFirst
方法中的Java流是否会按照map1
中描述的顺序运行findFirstUsingLoopV1
(map
不会针对所有元素运行)或如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;
}
collectUsingStreams
之后的collect
方法中的Java流将再次按f1
中所述的顺序运行f2
和collectUsingLoopV1
f1
在f2
之前评估}或collectUsingLoopV2
中描述(f2
之前评估f1
)?
在未来的Java版本中,该订单是否会发生变化,或者有一个官方文档可以保证我们f1
和f2
来电的顺序?
修改
感谢所有的答案和评论,但不幸的是,我仍然没有看到处理元素的顺序的良好解释。文档确实说,将为列表保留遭遇顺序,但它们不指定如何处理这些元素。例如,在findFirst
的情况下,文档保证map1
将首先看到1然后2但是它没有说明{4}将不会执行4和5.这是否意味着我们不能保证我们的处理顺序与我们期望的Java版本一样?可能是的。
答案 0 :(得分:12)
在未来的Java版本中,该订单是否会发生变化,或者有一个官方文档可以保证我们的map1调用顺序?
javadocs,包括package summaries(人们经常忽略那些),是API合同。可观察但未由javadoc定义的行为通常应被视为可能在将来版本中更改的实现细节。
因此,如果在javadocs中找不到它,则无法保证。
调用流管道阶段的顺序并且未指定交错。指定的是在哪种情况下保留所谓的encounter order流。假设有序流,仍允许实现执行任何将保留遭遇顺序的交错,批处理和内部重新排序。例如。 sorted(comparator).filter(predicate).findFirst()
可以在内部替换为filter(predicate).min(comparator)
,这当然会显着影响谓词和比较器的调用方式,并且即使在有序流中也会产生相同的结果。
这是否意味着我们无法保证我们的处理顺序与我们期望的Java版本一样?可能是的。
是的,这应该不是一个问题,因为大多数流API require callbacks to be stateless和free of side-effects,这意味着他们不应该关心流管道的内部执行顺序和结果应该是相同的,模数由无序流赋予的余地。
明确的要求和缺乏保证为JDK开发人员提供了如何实现流的灵活性。
如果你有任何特殊情况,那么你应该问一个更具体的问题,关于你想避免的执行重新排序。
您应该始终牢记流可以是并行的,例如:由第三方代码传递的实例,或者包含一个源或中间流操作,这个操作比理论上不那么懒(当前flatMap就是这样的操作)。如果有人提取和重新分割分裂器或使用Stream
接口的自定义实现,则流管道也可以包含自定义行为。
因此,当特定流实现在以特定方式使用它们时可能会出现一些可预测的行为,并且对于该特定情况的未来优化可能被认为是非常不可能的,这不会推广到所有可能的流管道,因此API无法提供这样的一般保证。