Что такое операция reduce в Java Stream API?

Ответ

reduce — это терминальная операция в Java Stream API, которая выполняет свёртку (агрегацию) элементов потока в одно значение, последовательно применяя функцию-аккумулятор.

Основные формы:

  1. С начальным значением (identity):

    T reduce(T identity, BinaryOperator<T> accumulator)
    • identity — начальное значение и результат для пустого потока.
    • accumulator — функция, объединяющая текущий результат и следующий элемент.
      List<Integer> numbers = List.of(1, 2, 3, 4);
      int sum = numbers.stream()
                   .reduce(0, (a, b) -> a + b); // Результат: 10
  2. Без начального значения:

    Optional<T> reduce(BinaryOperator<T> accumulator)
    • Возвращает Optional, так как для пустого потока результата нет.
      Optional<Integer> product = numbers.stream()
                                     .reduce((a, b) -> a * b); // Optional[24]

Ключевые моменты:

  • Использование: сумма, произведение, поиск максимума/минимума, конкатенация строк.
  • Порядок: В параллельных потоках accumulator должен быть ассоциативным, а identityнейтральным элементом (например, 0 для сложения, 1 для умножения).
  • Альтернативы: Для часто используемых операций (sum, max, min) существуют специализированные методы.

Ответ 18+ 🔞

Ну, слушай, смотри, вот есть у тебя поток, да? Stream там, numbers, хуйня какая-то. И тебе надо всё это добро в одну кучу сгрести. Сумму найти, или всё перемножить, или строки склеить. Вот для этого, блядь, и придумали reduce — операция терминальная, то есть после неё поток, сука, кончается, всё, приехали.

Как это, блядь, работает:

  1. С начальным значением (identity):

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

    Тут всё просто, как три копейки. identity — это твоя стартовая точка, база, хуле. Если поток пустой — вернётся именно это значение, и всё. А accumulator — это функция, которая берёт текущий накопленный результат (или identity на первом шаге) и следующий элемент из потока, и делает с ними что-то. Например, складывает, ёпта.

    List<Integer> numbers = List.of(1, 2, 3, 4);
    int sum = numbers.stream()
                     .reduce(0, (a, b) -> a + b); // Итог: 10

    Смотри: сначала a = 0 (наш identity), b = 1 (первый элемент). Сложили — получили 1. Теперь a = 1, b = 2 — сложили, 3. И так далее, пока не проебём все числа. В конце — 10, красота.

  2. Без начального значения:

    Optional<T> reduce(BinaryOperator<T> accumulator)

    А вот это уже, блядь, поинтереснее. Ты не даёшь стартового значения. А если поток пустой? А нихуя не из чего начинать! Поэтому результат заворачивают в Optional — типа, может что-то есть, а может и нет, мудак.

    Optional<Integer> product = numbers.stream()
                                       .reduce((a, b) -> a * b); // Optional[24]

    Работает так же, только первый шаг: a = 1 (первый элемент), b = 2 (второй). Перемножили — 2. Дальше a = 2, b = 3 — 6. Ну и a = 6, b = 4 — 24, ядрёна вошь!

На что обратить внимание, чтобы не обосраться:

  • Зачем это надо: Суммы, произведения, максимумы, конкатенация строк — везде, где надо всё схлопнуть в одно значение.
  • Параллельные потоки — головная боль: Если юзаешь параллельный стрим, твой accumulator должен быть ассоциативным (чтобы не важно было, в каком порядке складывать: (a + b) + c должно равняться a + (b + c)). А identity должен быть нейтральным элементом (например, 0 для сложения, 1 для умножения). Иначе получишь пиздец, а не результат.
  • Не выёбывайся: Для частых операций вроде sum(), max(), min() уже есть готовые методы — юзай их, не изобретай велосипед, ёпта.