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