有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java中间流操作未按计数计算

我似乎很难理解Java如何将流操作组合到流管道中

当执行以下代码时

public
 static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

控制台仅打印4StringBuilder对象仍然具有值""

当我添加过滤器操作时:filter(s -> true)

public static void main(String[] args) {
    StringBuilder sb = new StringBuilder();

    var count = Stream.of(new String[]{"1", "2", "3", "4"})
            .filter(s -> true)
            .map(sb::append)
            .count();

    System.out.println(count);
    System.out.println(sb.toString());
}

输出更改为:

4
1234

这种看似冗余的过滤操作如何改变合成流管道的行为


共 (3) 个答案

  1. # 1 楼答案

    在我的JDK版本中,count()终端操作最终执行以下代码:

    if (StreamOpFlag.SIZED.isKnown(helper.getStreamAndOpFlags()))
        return spliterator.getExactSizeIfKnown();
    return super.evaluateSequential(helper, spliterator);
    

    如果在操作管道中有一个filter()操作,那么最初已知的流的大小就无法再知道了(因为filter可能会拒绝流中的一些元素)。因此if块不被执行,中间操作被执行,因此StringBuilder被修改

    另一方面,如果管道中只有map(),则流中的元素数保证与初始元素数相同。因此执行if块,直接返回大小,而不评估中间操作

    请注意,传递给map()的lambda违反了文档中定义的约定:它应该是一个无干扰、无状态的操作,但不是无状态的。因此,在这两种情况下产生不同的结果不能被视为错误

  2. # 2 楼答案

    jdk-9中,它被清晰地记录在java文档中

    The eliding of side-effects may also be surprising. With the exception of terminal operations forEach and forEachOrdered, side-effects of behavioral parameters may not always be executed when the stream implementation can optimize away the execution of behavioral parameters without affecting the result of the computation. (For a specific example see the API note documented on the count operation.)

    API注释:

    An implementation may choose to not execute the stream pipeline (either sequentially or in parallel) if it is capable of computing the count directly from the stream source. In such cases no source elements will be traversed and no intermediate operations will be evaluated. Behavioral parameters with side-effects, which are strongly discouraged except for harmless cases such as debugging, may be affected. For example, consider the following stream:

     List<String> l = Arrays.asList("A", "B", "C", "D");
     long count = l.stream().peek(System.out::println).count();
    

    The number of elements covered by the stream source, a List, is known and the intermediate operation, peek, does not inject into or remove elements from the stream (as may be the case for flatMap or filter operations). Thus the count is the size of the List and there is no need to execute the pipeline and, as a side-effect, print out the list elements.

  3. # 3 楼答案

    这不是什么。地图是给你的。它应该被用来把一个“某物”流变成一个“其他东西”流。在本例中,您使用map将一个字符串附加到外部Stringbuilder,之后将有一个“Stringbuilder”流,每个“Stringbuilder”流都是通过向原始Stringbuilder附加一个数字的map操作创建的

    您的流实际上不会对流中的映射结果执行任何操作,因此完全有理由假设流处理器可以跳过该步骤。你指望副作用来完成这项工作,这打破了地图的功能模型。使用forEach做这件事会更好。将计数完全作为一个单独的流,或者在forEach中使用AtomicInt放置一个计数器

    过滤器强制它运行流内容,因为它现在必须对每个流元素执行一些概念上有意义的操作