Как работает метод limit () в Java 8? - PullRequest
21 голосов
/ 03 мая 2019

Я пытаюсь понять, как работает метод reduce() в .

Например, у меня есть этот код:

public class App {

    public static void main(String[] args) {
        String[] arr = {"lorem", "ipsum", "sit", "amet"};
        List<String> strs = Arrays.asList(arr);

        int ijk = strs.stream().reduce(0, 
            (a, b) -> { 
                System.out.println("Accumulator, a = " + a + ", b = " + b);
                return a + b.length();
            },
            (a, b) -> {
                System.out.println("Combiner");
                return a * b;
            });
        System.out.println(ijk); 
    }
}

И вывод такой:

Accumulator, a = 0, b = lorem
Accumulator, a = 5, b = ipsum
Accumulator, a = 10, b = sit
Accumulator, a = 13, b = amet
17

Это сумма длины этих строк.И я вижу, что объединитель не доступен, поэтому он не будет умножать числа, он только добавляет числа.

Но если у меня есть эти потоки:

int ijk = strs.parallelStream().reduce(0, 
    (a, b) -> { 
        System.out.println("Accumulator, a = " + a + ", b = " + b);
        return a + b.length();
    },
    (a, b) -> {
        System.out.println("Combiner");
        return a * b;
    });

System.out.println(ijk); 

Это вывод:

Accumulator, a = 0, b = ipsum
Accumulator, a = 0, b = lorem
Accumulator, a = 0, b = sit
Combiner
Accumulator, a = 0, b = amet
Combiner
Combiner
300

Я вижу, что к Аккумулятору и Комбинатору обращаются оба, но возвращается только умножение.Так что же происходит с суммой?

Ответы [ 4 ]

18 голосов
/ 03 мая 2019

Вы должны прочитать документацию reduce, которая гласит:

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

combiner.

В вашем случае вы нарушаете этот закон (выполняя сумму в accumulator и умножение в combiner), поэтому результат, который вы видите для такой операции действительно не определено и зависит от того, как реализован Spliterator для базового источника (не делайте этого!).

Кроме того, combiner - это только для параллельного потока.

Конечно, весь ваш подход можно упростить до:

Arrays.asList("lorem", "ipsum", "sit", "amet")
      .stream()
      .mapToInt(String::length)
      .sum();

Если вы делаете это только для учебных целей, правильным будет reduce (для получения sum):

strs.parallelStream()
    .reduce(0,
            (a, b) -> {
                  System.out.println("Accumulator, a = " + a + ", b = " + b);
                  return a + b.length();
            },
            (a, b) -> {
                  System.out.println("Combiner");
                  return a + b;
            });
10 голосов
/ 03 мая 2019

Ключевые понятия: личность, аккумулятор и объединитель

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

  • Идентичность - элемент, который является начальным значением операции сокращения и результатом по умолчанию, если поток пуст
  • itemAccumulator - функция, которая принимает два параметра: частичный результат операции сокращения и следующий элемент потока
  • Combiner - функция, которая принимает два параметра: частичный результат операции сокращения и следующий элемент потока Combiner - функция, используемая для объединения частичного результата операции сокращения при распараллеливании редукции или при несоответствии между типами аргументов аккумулятора и типами реализации аккумулятора

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

Случай 1: Комбинатор работает с parallelStream, как показано в вашем примере

Случай 2: пример аккумулятора с аргументами другого типа

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

List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());

Ошибка компиляции

The method reduce(User, BinaryOperator<User>) in the type Stream<User> is not applicable for the arguments (int, (<no type> partialAgeResult, <no type> user) -> {})

Мы можем решить эту проблему с помощью объединителя: это ссылка на метод Integer::sum или с помощью лямбда-выражения (a,b)->a+b

int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(),Integer::sum);

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

8 голосов
/ 03 мая 2019

Есть 3 способа уменьшить использование .В двух словах, Stream::reduce начинается с двух последовательных элементов (или значения идентификатора, одного с первым) и выполняет с ними операцию, создавая новое уменьшенное значение.Для каждого следующего элемента происходит то же самое, и выполняется операция с уменьшенным значением.

Допустим, у вас есть поток 'a', 'b', 'c' и 'd'.Сокращение выполняет следующую последовательность операций:

  1. result = operationOn('a', 'b') - operationOn может быть чем угодно (сумма длин входов ..)
  2. result = operationOn(result, 'c')
  3. result = operationOn(result, 'd')
  4. result is returned

Методы:

  • Optional<T> reduce(BinaryOperator<T> accumulator)выполняет сокращение на элементах.Начинается с первых двух элементов с уменьшенным значением, затем с каждого элемента с уменьшенным значением.Возвращается Optional<T>, поскольку не гарантируется, что входной поток не пустой.

  • T reduce(T identity, BinaryOperator<T> accumulator) выполняет те же действия, что и описанный выше метод, за исключением идентификаторазначение дается в качестве первого элемента.Возвращается T, поскольку всегда гарантируется хотя бы один элемент, поскольку T identity.

  • U reduce(U identity, BiFunction<U,? super T, U> accumulator, BinaryOperator<U> combiner) выполняет те же действия, что и вышес добавлением, что функции совмещены.Возвращается U, поскольку всегда гарантируется хотя бы один элемент из-за U identity.

2 голосов
/ 03 мая 2019

Я предполагаю, что вы решили сделать сложение и умножение просто как демонстрацию, чтобы увидеть, что именно происходит.

Как вы уже заметили и, как уже упоминалось, объединитель вызывается только для параллельных потоков.

Короче говоря, на параллельных каналах часть потока (или соответствующий Spliterator) обрезается и обрабатывается другим потоком. После обработки нескольких деталей их результат объединяется с помощью комбайнера.

В вашем случае все четыре элемента обрабатываются другим потоком, и тогда происходит комбинирование элементов. Вот почему вы не видите применения (кроме 0 +), а только умножения.

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

...