Объяснить java 8 собрать перегруженный метод [duplicate]

Вместо того, что вы сейчас используете, чтобы скрыть кнопку, было бы гораздо проще установить visibility: collapse; в атрибуте style. Тем не менее, я бы порекомендовал использовать немного простого Javascript для отправки формы. Насколько я понимаю, поддержка таких вещей сегодня повсеместна.

93
задан Stuart Marks 18 March 2017 в 21:07
поделиться

4 ответа

Две и три версии аргументов reduce, которые вы пытались использовать, не принимают один и тот же тип для accumulator.

Два аргумента reduce - , определенные как :

T reduce(T identity,
         BinaryOperator<T> accumulator)

В вашем случае T является строкой, поэтому BinaryOperator<T> должен принимать два аргумента String и возвращать String. Но вы передаете ему int и String, что приводит к ошибке компиляции - argument mismatch; int cannot be converted to java.lang.String. На самом деле, я думаю, что передача 0, поскольку значение идентичности здесь также неверно, поскольку ожидается String (T).

Также обратите внимание, что эта версия сокращает процесс потока Ts и возвращает T, поэтому вы не может использовать его, чтобы уменьшить поток String до int.

Три аргумента reduce - , определенные как :

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

In ваш случай U является Integer и T является String, поэтому этот метод уменьшит поток String до Integer.

Для аккумулятора BiFunction<U,? super T,U> вы можете передавать параметры двух разных типов (U и? super T ), которые в вашем случае являются целыми и строковыми. Кроме того, значение U идентичности принимает целочисленное значение в вашем случае, поэтому передача его 0 прекрасна.

Другой способ достижения желаемого:

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .reduce(0, (accumulatedInt, len) -> accumulatedInt + len);

Здесь тип поток соответствует типу возврата reduce, поэтому вы можете использовать две версии параметров reduce.

Конечно, вам не нужно использовать reduce вообще:

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .sum();
52
ответ дан Eran 15 August 2018 в 18:10
поделиться
  • 1
    В качестве второго варианта в вашем последнем коде вы также можете использовать mapToInt(String::length) по сравнению с mapToInt(s -> s.length()), не уверен, что лучше быть лучше другого, но я предпочитаю, чтобы первый читал. – skiwi 19 June 2014 в 16:35
  • 2
    Многие найдут этот ответ, поскольку они не понимают, почему нужен combiner, почему недостаточно иметь accumulator. В этом случае: объединитель необходим только для параллельных потоков, чтобы объединить "накопленный" результаты потоков. – ddekany 24 November 2017 в 15:54
  • 3
  • 4

Ответ Эрана описал различия между версиями reduce с двумя аргументами и тремя аргументами в том, что первая уменьшает Stream<T> до T, тогда как последняя уменьшает Stream<T> до U. Тем не менее, на самом деле он не объяснил необходимость дополнительной функции объединителя при уменьшении Stream<T> до U.

. Один из принципов проектирования API Streams заключается в том, что API не должен отличаться последовательным и параллельным потокам или другим способом, конкретный API не должен препятствовать правильной работе потока последовательно или параллельно. Если ваши лямбды имеют правильные свойства (ассоциативные, неинтерферирующие и т. Д.), Поток, выполняемый последовательно или параллельно, должен давать одинаковые результаты.

Давайте сначала рассмотрим версию сокращения с двумя аргументами:

T reduce(I, (T, T) -> T)

Последовательная реализация проста. Значение идентичности I «накапливается» с элементом нулевого потока, чтобы дать результат. Этот результат накапливается с первым элементом потока, чтобы дать другой результат, который, в свою очередь, накапливается со вторым элементом потока и т. Д. После того, как последний элемент накоплен, возвращается окончательный результат.

Параллельная реализация начинается с разделения потока на сегменты. Каждый сегмент обрабатывается собственным потоком последовательным образом, описанным выше. Теперь, если у нас есть N потоков, мы имеем N промежуточных результатов. Их необходимо сократить до одного результата. Поскольку каждый промежуточный результат имеет тип T, и у нас есть несколько, мы можем использовать одну и ту же функцию аккумулятора, чтобы уменьшить эти промежуточные результаты до одного результата.

Теперь рассмотрим гипотетическую операцию сокращения двух аргументов что уменьшает Stream<T> до U. В других языках это называется операцией «fold» или «fold-left», поэтому я назову ее здесь. Обратите внимание, что это не существует в Java.

U foldLeft(I, (U, T) -> U)

(Обратите внимание, что значение идентификатора I имеет тип U.)

Последовательная версия foldLeft как последовательная версия reduce, за исключением того, что промежуточные значения имеют тип U вместо типа T. Но в остальном это одно и то же. (Гипотетическая операция foldRight будет аналогичной, за исключением того, что операции будут выполняться справа налево, а не слева направо.)

Теперь рассмотрим параллельную версию foldLeft. Начнем с разделения потока на сегменты. Затем мы можем иметь, что каждый из N потоков уменьшает значения T в своем сегменте на N промежуточных значений типа U. Теперь что? Как мы получаем от N значений типа U до одного результата типа U?

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

U reduce(I, (U, T) -> U, (U, U) -> U)

Или, используя синтаксис Java:

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

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

Наконец, Java не предоставляет операции foldLeft и foldRight, потому что они подразумевают конкретный порядок операций, который по своей сути является последовательным. Это противоречит описанному выше принципу проектирования предоставления API, которые поддерживают последовательную и параллельную работу в равной степени.

135
ответ дан Community 15 August 2018 в 18:10
поделиться
  • 1
    plus1 Это делает многое понятным, спасибо! – naikus 15 December 2014 в 07:28
  • 2
    Итак, что вы можете сделать, если вам нужен foldLeft, потому что вычисление зависит от предыдущего результата и не может быть распараллелировано? – amoebe 9 May 2015 в 18:51
  • 3
    @amoebe Вы можете реализовать свой собственный foldLeft, используя forEachOrdered. Однако промежуточное состояние должно храниться в захваченной переменной. – Stuart Marks 10 May 2015 в 03:25
  • 4
    @StuartMarks спасибо, я закончил использовать jOOλ. У них есть опрятная реализация foldLeft . – amoebe 10 May 2015 в 13:12
  • 5
    Любите этот ответ! Исправьте меня, если я ошибаюсь: это объясняет, почему пример запуска OP (второй) никогда не будет вызывать объединитель при запуске, являясь потоком последовательным. – Luigi Cortese 25 November 2015 в 13:10
  • 6

Так как мне нравятся каракули и стрелки, чтобы прояснить понятия ... давайте начнем!

От String to String (последовательный поток)

Предположим, что у вас есть 4 строки: ваша цель - объединить такие строки в одну. В основном вы начинаете с типа и заканчиваете с тем же типом.

Вы можете добиться этого с помощью

String res = Arrays.asList("one", "two","three","four")
        .stream()
        .reduce("",
                (accumulatedStr, str) -> accumulatedStr + str);  //accumulator

, и это поможет вам визуализировать происходящее:

Функция аккумулятора преобразует, шаг за шагом, элементы в вашем (красном) потоке до конечного уменьшенного (зеленого) значения. Функция аккумулятора просто преобразует объект String в другой String.

От String to int (параллельный поток)

Предположим, что у вас есть те же 4 строки: ваша новая цель - суммируйте их длины, и вы хотите распараллелить свой поток.

Что вам нужно, это примерно так:

int length = Arrays.asList("one", "two","three","four")
        .parallelStream()
        .reduce(0,
                (accumulatedInt, str) -> accumulatedInt + str.length(),                 //accumulator
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2); //combiner

, и это схема происходящего

Здесь функция аккумулятора (a BiFunction) позволяет преобразовать ваши данные String в данные int. Будучи параллельным потоком, он разделяется на две (красные) части, каждая из которых разрабатывается независимо друг от друга и производит столько же частичных (оранжевых) результатов. Определение объединителя необходимо для предоставления правила объединения частичных результатов int в конечный (зеленый) int.

От String to int (последовательный поток)

Что если вы не хотите распараллелить свой поток? Ну, комбайнер должен быть предоставлен в любом случае, но он никогда не будет вызван, если не будет получено никаких частичных результатов.

66
ответ дан Luigi Cortese 15 August 2018 в 18:10
поделиться
  • 1
    Спасибо за это. Мне даже не нужно было читать. Я бы хотел, чтобы они просто добавили функцию freaking fold. – Lodewijk Bogaards 4 March 2016 в 15:57
  • 2
    @LodewijkBogaards рад, что это помогло! JavaDoc здесь довольно загадочно – Luigi Cortese 4 March 2016 в 16:31
  • 3
    Мне понравилось ваше объяснение .. Спасибо .. – AnkitRox 8 September 2016 в 10:40
  • 4
    @LuigiCortese В параллельном потоке он всегда делит элементы на пары? – TheLogicGuy 18 May 2017 в 11:40

Нет версии reduce , которая принимает два разных типа без комбайнера , поскольку он не может выполняться параллельно (не уверен, почему это требование). Тот факт, что аккумулятора должен быть ассоциативным, делает этот интерфейс практически бесполезным, поскольку:

list.stream().reduce(identity,
                     accumulator,
                     combiner);

Производит те же результаты, что и:

list.stream().map(i -> accumulator(identity, i))
             .reduce(identity,
                     combiner);
0
ответ дан quiz123 15 August 2018 в 18:10
поделиться
  • 1
    Такой map трюк, зависящий от конкретных accumulator и combiner, может существенно замедлить работу. – Tagir Valeev 4 September 2015 в 15:51
  • 2
    Или значительно ускорите его, так как теперь вы можете упростить accumulator, отбросив первый параметр. – quiz123 4 September 2015 в 16:27
  • 3
    Параллельное сокращение возможно, это зависит от ваших вычислений. В вашем случае вы должны знать сложность объединителя, но также и накопителя по идентичности и другим экземплярам. – LoganMzz 27 June 2017 в 11:59
Другие вопросы по тегам:

Похожие вопросы: