在Java 8中,Stream有一个方法reduce:
T reduce(T identity, BinaryOperator<T> accumulator);
是否允许累加器运算符修改其任一参数?我认为不是因为JavaDoc说累加器应该是NonInterfering,尽管所有的例子都谈到修改集合,而不是修改集合的元素。
所以,举一个具体的例子,如果我们有
integers.reduce(0, Integer::sum);
并且假设Integer
是可变的,sum
是否允许通过向其添加第二个参数的值(原地)来修改其第一个参数?
我认为没有,但我还想举例说明这种干扰会导致问题。
答案 0 :(得分:12)
没有。累加器不应修改其参数;它需要两个值并产生一个新值。如果你想在累积过程中使用变异(例如,将字符串累积到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 + b
和0 + c + d
,然后添加结果。但是如果它们共享相同的标识值,并且该值由于其中一个计算而发生变化,则另一个可能以错误的值开始。
(进一步注意,即使对于顺序计算,也允许实现这样做,如果它认为值得的话。)
答案 1 :(得分:0)
这在语法上是允许的,但我认为它违背了设计模式并且是一个坏主意。
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。点[X = 10,Y = 37]]