Java流在终端点执行的操作顺序

tso*_*akp 6 java java-8 java-stream

我一直试图从官方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;
    }
Run Code Online (Sandbox Code Playgroud)

findFirstUsingStreams后面的方法中的Java流findFirst是否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;
}
Run Code Online (Sandbox Code Playgroud)

再次将在Java流collectUsingStreams的方法之后collect被调用的运行f1f2在所描述的顺序collectUsingLoopV1(f1之前评价f2),或者如上述collectUsingLoopV2(f2之前被评估f1)?

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

编辑

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

the*_*472 12

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

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

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

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

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

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

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

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


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

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

  • 这是排序的一个有趣的例子!加一. (2认同)