Подтвердить что ты не робот

Назначение третьего аргумента функции "уменьшить" в функциональном программировании Java 8

В каких обстоятельствах является третьим аргументом для "сокращения", вызванного в потоках Java 8?

Приведенный ниже код пытается пересечь список строк и складывать значения кодовой точки первого символа каждого из них. Значение, возвращаемое финальной лямбдой, никогда не используется, и если вы вставляете println, это никогда не будет вызвано. Документация описывает его как "объединитель", но я не могу найти более подробную информацию...

int result =
  data.stream().reduce(0, (total,s) -> total + s.codePointAt(0), (a,b) -> 1000000); 
4b9b3361

Ответ 1

Вы говорите эту функцию?

reduce <U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner) 

Выполняет сокращение элементов этого потока, используя предоставленную идентификацию, накопление и объединение функций. Это эквивалентно:

 U result = identity;
 for (T element : this stream)
     result = accumulator.apply(result, element)
 return result;   

но не ограничивается последовательностью выполнения. Значение идентификатора должно быть идентификатором функции объединителя. Эта означает, что для всех u сумматор (тождество, u) равен u. Кроме того, функция объединителя должна быть совместима с аккумуляторная функция; для всех u и t должно выполняться следующее:

 combiner.apply(u, accumulator.apply(identity, t)) == 
     accumulator.apply(u, t)   

Это операция терминала.

API Примечание. Многие сокращения, использующие эту форму, могут быть представлены более просто путем явной комбинации операций отображения и сокращения. функция аккумулятора действует как плавкий преобразователь и аккумулятор, который может иногда более эффективны, чем отдельное отображение и сокращение, поскольку, зная ранее уменьшенное значение, вы можете избежать некоторых вычисление. Тип Параметры: U - Тип результата Параметры: identity - идентификационное значение для накопителя функций объединителя - ассоциативная, невмешающая, функция без гражданства для включения дополнительный элемент в объединитель результатов - ассоциативный, неинтерферирующая функция без сохранения для объединения двух значений, которые должен быть совместим с функцией аккумулятора. Возвращает: результат сокращения. См. также: уменьшить (BinaryOperator), уменьшить (Object, BinaryOperator)

Я предполагаю, что его цель - разрешить параллельное вычисление, и поэтому я предполагаю, что он используется только в том случае, если редукция выполняется параллельно. Если он выполняется последовательно, нет необходимости использовать combiner. Я не знаю этого наверняка - я просто догадываюсь на основе комментария к доктору: "[...] не ограничено выполнение последовательно" и многие другие упоминания о "параллельном выполнении" в комментариях.

Ответ 2

Я думаю, Операции сокращения от java.util.stream сводка пакетов может ответить на вопрос. Позвольте мне привести наиболее важную часть здесь:


В более общем виде операция сокращения для элементов типа <T>, дающая результат типа <U>, требует трех параметров:

<U> U reduce(U identity,
              BiFunction<U, ? super T, U> accumulator,
              BinaryOperator<U> combiner);

Здесь элемент идентификации является как начальным начальным значением для сокращения, так и результатом по умолчанию, если нет входных элементов. Функция аккумулятора принимает частичный результат и следующий элемент и производит новый частичный результат. Функция объединителя объединяет два частичных результата для получения нового частичного результата. (Объединитель необходим в параллельных сокращениях, где вход разделен, частичное накопление, рассчитанное для каждого раздела, а затем частичные результаты объединяются для получения окончательного результата.) Более формально значение идентификатора должно быть тождеством для функции объединителя. Это означает, что для всех u combiner.apply(identity, u) равно u. Кроме того, функция объединителя должна быть ассоциативной и должна быть совместима с функцией аккумулятора: для всех u и t, combiner.apply(u, accumulator.apply(identity, t)) должен быть equals() до accumulator.apply(u, t).

Форма с тремя аргументами является обобщением формы с двумя аргументами, включающей шаг отображения на этап накопления. Мы могли бы повторить простой пример суммы весов, используя более общую форму следующим образом:

 int sumOfWeights = widgets.stream()
                           .reduce(0,
                                   (sum, b) -> sum + b.getWeight())
                                   Integer::sum);

хотя явная форма сокращения карты более читаема и поэтому обычно должна быть предпочтительной. Обобщенная форма предоставляется для случаев, когда значительная работа может быть оптимизирована путем объединения отображения и сокращения в одну функцию.


Другими словами, насколько я понимаю, форма трех аргументов полезна в двух случаях:

  • При параллельном выполнении.
  • Когда значительная оптимизация производительности может быть достигнута путем объединения шагов сопоставления и накопления. В противном случае может использоваться более простая и читаемая явная форма сокращения карты.

Явная форма упоминается ранее в одном документе:

int sumOfWeights = widgets.parallelStream()
        .filter(b -> b.getColor() == RED)
        .mapToInt(b -> b.getWeight())
        .sum();

Ответ 3

Простой тестовый код для подтверждения использования объединителя:

String[] strArray = {"abc", "mno", "xyz"};
List<String> strList = Arrays.asList(strArray);

System.out.println("stream test");
int streamResult = strList.stream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
    );
System.out.println("streamResult: " + streamResult);

System.out.println("parallelStream test");
int parallelStreamResult = strList.parallelStream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "]"); return 1000000;}
    );
System.out.println("parallelStreamResult: " + parallelStreamResult);

System.out.println("parallelStream test2");
int parallelStreamResult2 = strList.parallelStream().reduce(
        0, 
        (total,s) -> { System.out.println("accumulator: total[" + total + "] s[" + s + "] s.codePointAt(0)[" + s.codePointAt(0) + "]"); return total + s.codePointAt(0); }, 
        (a,b) -> { System.out.println("combiner: a[" + a + "] b[" + b + "] a+b[" + (a+b) + "]"); return a+b;}
    );
System.out.println("parallelStreamResult2: " + parallelStreamResult2);

Вывод:

stream test
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[97] s[mno] s.codePointAt(0)[109]
accumulator: total[206] s[xyz] s.codePointAt(0)[120]
streamResult: 326
parallelStream test
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
combiner: a[109] b[120]
combiner: a[97] b[1000000]
parallelStreamResult: 1000000
parallelStream test2
accumulator: total[0] s[mno] s.codePointAt(0)[109]
accumulator: total[0] s[xyz] s.codePointAt(0)[120]
accumulator: total[0] s[abc] s.codePointAt(0)[97]
combiner: a[109] b[120] a+b[229]
combiner: a[97] b[229] a+b[326]
parallelStreamResult2: 326