Java 8 中的 reduce 累加器是否允许修改其参数?

2022-09-03 02:30:41

在Java 8中,Stream有一个方法 reduce:

T reduce(T identity, BinaryOperator<T> accumulator);

是否允许累加器运算符修改其任一参数?我推测不是,因为JavaDoc说累加器应该是NonInterfering,尽管所有的例子都谈到修改集合,而不是修改集合的元素。

所以,举一个具体的例子,如果我们有

 integers.reduce(0, Integer::sum);

并假设在一段时间内,可变的,是否允许通过向其添加(就地)其第二个参数的值来修改其第一个参数?Integersum

我推测不是,但我也想举个例子来说明这种干扰会导致问题的地方。


答案 1

不。累加器不应修改其参数;它采用两个值并生成一个新值。如果要在累积过程中使用突变(例如,将字符串累积到 StringBuffer 中而不是串联),请使用 ,这是为此而设计的。Stream.collect()

下面是一个代码示例,如果您尝试这样做,则会生成错误的答案。假设你想用一个假设的可变Integer类做加法:

// Don't do this
MutableInteger result = stream.reduce(new MutableInteger(0), (a,b) -> a.add(b.get()));

这得到错误答案的一个原因是,如果我们并行分解计算,现在两个计算共享相同的可变起始值。请注意:

a + b + c + d
= 0 + a + b + 0 + c + d  // 0 denotes identity
= (0 + a + b) + (0 + c + d) // associativity

因此,我们可以自由地拆分流,计算部分和,然后添加结果。但是,如果它们共享相同的标识值,并且该值由于其中一个计算而发生突变,则另一个值可能以错误的值开头。0 + a + b0 + c + d

(进一步注意,如果实现认为值得,即使对于顺序计算,也可以这样做。


答案 2

这在语法上是允许的,但我认为它与设计模式背道而驰,是一个坏主意。

  static void accumulatorTest() {
     ArrayList<Point> points = new ArrayList<>();
     points.add(new Point(5, 6));
     points.add(new Point(0, 6));
     points.add(new Point(1, 9));
     points.add(new Point(4, 16));
     BinaryOperator<Point> sumPoints = new BinaryOperator<Point>() {
        public Point apply(Point p1, Point p2) {
           p2.x += p1.x;
           p2.y += p1.y;
           return new Point(p2); //return p2 and the list is transformed into running total
        }
     };
     Point sum = points.stream().reduce(new Point(0, 0), sumPoints); 
     System.out.println(sum);
     System.out.println(points);
  }

答案是正确的。我们得到所有x和y坐标的总和。原始列表被修改,由输出确认:

java.awt.Point[x=10,y=37] [java.awt.Point[x=5,y=6], java.awt.Point[x=5,y=12], java.awt.Point[x=6,y=21], java.awt.Point[x=10,y=37]]