繁体   English   中英

未按计数评估中间流操作

[英]Intermediate stream operations not evaluated on count

我似乎无法理解 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());
}

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

当我添加过滤操作时: 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

这个看似多余的过滤操作如何改变组合流管道的行为?

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

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

如果在操作管道中存在filter()操作,则最初已知的流的大小将无法再知道(因为filter可能会拒绝流的某些元素)。 所以不执行if块,执行中间操作,从而修改 StringBuilder。

另一方面,如果管道中只有map() ,则流中的元素数量保证与初始元素数量相同。 所以执行了if块,直接返回size,不求中间操作。

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

jdk-9 中,它清楚地记录在 java docs 中

消除副作用也可能令人惊讶。 除了 forEach 和 forEachOrdered 的终端操作之外,当流实现可以在不影响计算结果的情况下优化掉行为参数的执行时,行为参数的副作用可能并不总是被执行。 (有关特定示例,请参阅有关计数操作的 API 说明。)

API注意事项:

如果能够直接从流源计算计数,则实现可以选择不执行流管道(顺序或并行)。 在这种情况下,不会遍历源元素,也不会评估中间操作。 强烈建议不要使用具有副作用的行为参数,除了调试等无害情况外,可能会受到影响。 例如,考虑以下流:

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

流源(一个 List)覆盖的元素数量是已知的,中间操作 peek 不会从流中注入或删除元素(对于 flatMap 或过滤器操作可能就是这种情况)。 因此,计数是列表的大小,并且不需要执行管道,并且作为副作用,打印出列表元素。

这不是 .map 的用途。 它应该用于将“Something”流转换为“Something Else”流。 在这种情况下,您使用 map 将字符串附加到外部 Stringbuilder,之后您有一个“Stringbuilder”流,每个流都是由 map 操作创建的,将一个数字附加到原​​始 Stringbuilder。

您的流实际上并未对流中的映射结果执行任何操作,因此假设流处理器可以跳过该步骤是完全合理的。 您依靠副作用来完成这项工作,这打破了地图的功能模型。 使用 forEach 执行此操作会更好地为您服务。 将计数完全作为单独的流进行,或者在 forEach 中使用 AtomicInt 放置一个计数器。

过滤器强制它运行流内容,因为它现在必须对每个流元素做一些理论上有意义的事情。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM