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

nee*_*rle 2 java parallel-processing reduce java-8 java-stream

问题很简单:为什么我们不能在流中的操作中使用StringBuilder(...)as ,但可以用作?identity functionreduce(...)java8string1.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)));
Run Code Online (Sandbox Code Playgroud)

这是执行上述行后的输出:

 123
 321321321321   // output when StringBuilder() is used as Identity
 123
Run Code Online (Sandbox Code Playgroud)

builder.append(string)是一个关联操作str1.concat(str2)。那么为什么concat有效而append无效呢?

Swe*_*per 9

是的,append 确实的关联,但这并不是由于储液器和组合传递函数的唯一要求。根据文档,它们必须是:

  • 联想
  • 不干扰
  • 无国籍

append不是无国籍的。它是有状态的。当你这样做sb.append("Hello"),它不仅返回一个StringBuilderHello追加到末尾,它也改变了内容的(即状态) sb

也来自文档

如果流操作的行为参数是有状态的,则流管道结果可能是不确定的或不正确的。有状态的 lambda(或其他实现适当功能接口的对象)的结果取决于在流管道执行期间可能发生变化的任何状态。

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

combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
Run Code Online (Sandbox Code Playgroud)

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

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

无论如何,这是一个可变减少的用例collect

System.out.println((StringBuilder)list.stream().parallel()
    .collect(
        StringBuilder::new, 
        StringBuilder::append, 
        StringBuilder::append
    )
);
Run Code Online (Sandbox Code Playgroud)

  • 作为附录:一旦执行了带有非空参数的第一个“append”,“StringBuilder”就不再是有效的*标识值*,因此它也违反了三参数“的第一个参数”的要求reduce` 必须是一个身份值。 (8认同)
  • @neerajdorle 是的,霍尔格就是这么说的。现在想来,我应该先用这个论点。这更容易表达:D (2认同)