Разрешено ли аккумулятору 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]]