java - 在 reduce 操作中使用 StringBuilder(...) 作为标识值会产生不可预测的结果

标签 java java-8 parallel-processing java-stream reduce

问题很简单:为什么我们不能使用 StringBuilder(...)作为identity functionreduce(...) java8 中的操作流,但是string1.concat(string2)可以用作 identity function

string1.concat(string2)可以看作类似于builder.append(string) (虽然据了解这些操作几乎没有什么区别),但我无法理解 reduce 操作的区别。考虑以下示例:

  List<String> list = Arrays.asList("1", "2", "3"); 
  
  // Example using the string concatenation operation
  System.out.println(list.stream().parallel()
            .reduce("", (s1, s2) -> s1 + s2, (s1, s2)->s1 + s2));

  // The same example, using the StringBuilder
  System.out.println(list.stream() .parallel()
            .reduce(new StringBuilder(""), (builder, s) -> builder
                    .append(s),(builder1, builder2) -> builder1
                    .append(builder2)));
 
 // using the actual concat(...) method
 System.out.println(list.stream().parallel()
            .reduce("", (s1, s2) -> s1.concat(s2), (s1, s2)->s1.concat(s2)));

这是执行上面几行后的输出:

 123
 321321321321   // output when StringBuilder() is used as Identity
 123

builder.append(string)是一个关联操作 str1.concat(str2)是。那为什么concat工作和append不是吗?

最佳答案

是的,append 确实是关联的,但这并不是作为累加器和组合器传递的函数的唯一要求。根据docs ,它们必须是:

  • 联想
  • 无干扰
  • 无国籍

append 不是无状态的。它是有状态的。当您执行 sb.append("Hello") 时,它不仅会返回一个 StringBuilder 并在末尾附加 Hello它还会更改 sb 的内容(即状态)。

同样来自 docs :

Stream pipeline results may be nondeterministic or incorrect if the behavioral parameters to the stream operations are stateful. A stateful lambda (or other object implementing the appropriate functional interface) is one whose result depends on any state which might change during the execution of the stream pipeline.

也因此,一旦应用了累加器或组合器,new StringBuilder() 就不是有效标识。一些东西会被添加到空字符串构建器中,并且不再满足所有身份必须满足的以下等式:

combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)

在调用累加器和/或组合器之后,并行流可能会使用旧的字符串生成器,并希望它们的内容不会更改。但是,累加器和组合器会改变字符串构建器,导致流产生不正确的结果。

另一方面,concat 满足以上所有三个条件。它是无状态的,因为它不会更改调用它的字符串。它只是重新调整一个新的连接字符串。 (String 无论如何都是不可变的,无法更改 :D)

无论如何,这是mutable reduction的一个用例使用 collect:

System.out.println((StringBuilder)list.stream().parallel()
    .collect(
        StringBuilder::new, 
        StringBuilder::append, 
        StringBuilder::append
    )
);

关于java - 在 reduce 操作中使用 StringBuilder(...) 作为标识值会产生不可预测的结果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63556636/

相关文章:

java - 使用 Java 8 实现递归 lambda 函数

java - 使用java流的单词排列

java - 运行 gradle 6,7,8 时设置 jdk8 的路径是什么意思?

java - 测试是否发出了良好的查询

java - 范围网络服务

java - 解释性能差异

c openmp - while 循环的并行化

asp.net -parallel.for 是如何工作的

c++ - 将 MPI 数据类型返回给 MPI Gather

java - 从可运行类更新 swing JEditorPane