Разрешено ли аккумулятору reduce в Java 8 изменять его аргументы?
в Java 8 поток имеет метод reduce:
T reduce(T identity, BinaryOperator<T> accumulator);
разрешено ли оператору аккумулятора изменять любой из его аргументов? Я предполагаю, что нет, так как JavaDoc говорит, что аккумулятор должен быть невмешательством, хотя все примеры говорят об изменении коллекции, а не об изменении элементов коллекции.
Итак, для конкретного примера, если у нас есть
integers.reduce(0, Integer::sum);
и предположим на миг, что Integer
была изменчива, бы sum
пустят изменить его первый параметр, добавив к нему (на месте) значение его второго параметра?
Я предполагаю, что нет, но я также хотел бы пример того, где это вмешательство вызывает проблему.
2 ответов
нет. Аккумулятор не должен изменять свои аргументы; он принимает два значения и создает новое значение. Если вы хотите использовать мутацию в процессе накопления (например, накапливая строки в 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
, а затем сложите результаты. Но если они используют одно и то же значение идентификатора, и это значение мутирует в результате одного из вычислений, другое может начинаться с неправильного значения.
(заметим, далее, что реализация будет позволена сделать это даже для последовательных вычислений, если она сочтет это целесообразным.)
это разрешено синтаксически, но я думаю, что это противоречит шаблону дизайна и является плохой идеей.
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.ОУ.Точка[x=10, y=37] [ява.ОУ.Точка[x=5, y=6], java.ОУ.Точка[x=5, y=12], java.ОУ.Точка[x=6, y=21], java.ОУ.Точка[x=10, y=37]]