Как работает метод limit() в Java 8? - программирование

Как работает метод limit() в Java 8?

Я пытаюсь понять, как работает метод redu 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

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

4b9b3361

Ответ 1

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

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

combiner.apply (и, аккумулятор, идентификатор (тождество, т)) == аккумулятор.применить (и, т)

В вашем случае вы нарушаете этот закон (делая сумму в 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;
            });

Ответ 2

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

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

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

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

Случай 1: Combiner работает с 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);

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

Ответ 3

Есть 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

Ответ 4

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

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

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

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

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

Ответ 5

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

Вы можете также предпочесть эту ссылку: - https://www.concretepage.com/java/jdk-8/java-8-stream-reduce-example

или: - https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html