[英]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.