Java 8中的减少累加器是否允许修改它的参数?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (13)

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

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

累加器运算符是否允许修改它的任何参数?我认为不是因为JavaDoc说累加器应该是NonInterfering,尽管所有示例都讨论修改集合,而不是修改集合的元素。

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

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

并假设暂时Integer是可变的,将sum被允许修改其第一个参数,方法是将其第二个参数的值添加到(就地)中?

我认为不是,但我也想要一个这种干扰导致问题的例子。

提问于
用户回答回答于

不可以。累加器不应该修改它的参数; 它需要两个值并产生一个新的值。如果你想在积累过程中使用突变(例如,将字符串累加到一个StringBuffer中而不是连接),可以使用Stream.collect()专门为此设计的。

这是一个代码,如果你尝试这个,会产生错误的答案。假设您想要使用假设的MutableInteger类进行添加:

// 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,然后添加结果。但是,如果他们共享相同的身份值,并且该值由于其中一个计算而发生变化,则另一个可能会以错误的值开始。

(进一步注意,如果它认为这是值得的,那么即使对于顺序计算,实现也是允许的。)

用户回答回答于

这在语法上是允许的,但我认为它违背了设计模式并且是一个糟糕的主意。

  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]]

扫码关注云+社区