Является ли накопитель сокращения в 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]]