Как применяется операция reduce в Java Stream API?

Ответ

Операция reduce в Java Stream API используется для свертки (агрегации) элементов потока в одно результирующее значение. Это терминальная операция.

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

  1. 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
    // Эквивалентно: 0 + 1 = 1, 1 + 2 = 3, 3 + 3 = 6, 6 + 4 = 10
  2. Optional<T> reduce(BinaryOperator<T> accumulator)

    • Используется, когда нет естественного начального значения (identity). Возвращает Optional, так как поток может быть пустым.
    // Поиск максимального значения
    Optional<Integer> max = numbers.stream().reduce(Integer::max);
    max.ifPresent(System.out::println); // Выведет 4
  3. <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

    • Наиболее общая форма, нужна для параллельных стримов. combiner объединяет частичные результаты, полученные в разных потоках.
    // Конкатенация строк в параллельном потоке
    List<String> words = List.of("Hello", " ", "World", "!");
    String sentence = words.parallelStream()
            .reduce("", (partialString, element) -> partialString + element, String::concat);
    System.out.println(sentence); // "Hello World!"

Практические примеры и нюансы:

  • Используйте ссылки на методы: Integer::sum, Integer::max вместо лямбд.
  • identity должен быть нейтральным элементом: Для сложения — 0, для умножения — 1. Иначе результат в параллельных стримах будет некорректен.
  • Альтернативы reduce: Часто более удобны специализированные операции sum(), min(), max(), collect(). reduce хорош для кастомной логики агрегации.
// Кастомная агрегация: объединение объектов
List<Item> items = ...;
Basket totalBasket = items.stream()
        .reduce(new Basket(), // identity - пустая корзина
                (basket, item) -> basket.add(item), // accumulator
                Basket::merge); // combiner для параллельного выполнения

Ответ 18+ 🔞

Да ты посмотри на эту штуку, reduce! Ну прям как тот самый Герасим, блядь, из рассказа — молчаливый, здоровый, а внутри ебать какая мощь скрыта! Собирает всё в кучу, как тот немой в мешок, и выдаёт один результат. Красота, ёпта!

Вот, смотри, какие у него три основные формы, как три богатыря, блядь:

  1. T reduce(T identity, BinaryOperator<T> accumulator) Это как прийти в магазин с деньгами, сука. identity — это твоя начальная сумма в кармане, даже если ты нихуя не купил, она у тебя есть. А accumulator — это кассирша, которая к твоей сумме прибавляет цену каждой новой хуйни из корзины.

    // Считаем бабки, потраченные на пиво
    List<Integer> ценыНаПиво = List.of(100, 150, 200, 80);
    int итоговаяАгония = ценыНаПиво.stream().reduce(0, (ужеНабрал, ещёБутылка) -> ужеНабрал + ещёБутылка); // Итог: 530
    // Считаем: 0 + 100 = 100, 100 + 150 = 250, 250 + 200 = 450, 450 + 80 = 530. Пиздец, дорого.
  2. Optional<T> reduce(BinaryOperator<T> accumulator) А это форма для максималистов, как тот самый Герасим, блядь! Начального значения нет — либо найдёшь что-то в потоке, либо нихуя. Возвращает Optional, потому что поток может быть пустым, как твои карманы после той же кальянной.

    // Ищем самую дорогую бутылку в баре (максимальное значение)
    Optional<Integer> самаяДорогаяПосудина = ценыНаПиво.stream().reduce(Integer::max);
    самаяДорогаяПосудина.ifPresent(цену -> System.out.println("А эта стоила: " + цену)); // Выведет: 200
  3. <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) О, это уже для распараллеливания, когда задач до овердохуища! identity — начальное состояние, accumulator — как каждый поток по отдельности копит свой результат, а combiner — это главный, который потом сводит все эти кучи в одну большую кучу. Без него в параллельных стримах — пиздец и хаос.

    // Собираем предложение из слов, и пусть каждый поток поработает
    List<String> слова = List.of("Ну", "чё", ",", "пошли", ",", "что", "ли", "?");
    String предложение = слова.parallelStream()
            .reduce("", (частьФразы, слово) -> частьФразы + слово, String::concat); // combiner склеивает куски
    System.out.println(предложение); // "Ну чё, пошли, что ли?"

А теперь, блядь, практические советы, чтобы не облажаться:

  • Используй ссылки на методы, не выёбывайся с лямбдами: Вместо (a, b) -> a + b пиши Integer::sum. Красиво и понятно, как слон в посудной лавке.
  • identity должен быть нейтральным, как швейцарец! Для сложения — 0, для умножения — 1. Если начнёшь с 10, то в параллельном стриме каждый поток начнёт с 10, и в итоге насчитаешь хуй знает что. Получишь не сумму, а пиздопроёбину.
  • Не забывай про альтернативы! Часто проще использовать готовые операции: sum(), min(), collect(). reduce — это когда тебе нужна своя, особенная, ебучка-аггрегация, которую стандартные методы не делают.
// Допустим, у нас список покупок, и мы хотим собрать одну большую тележку
List<Покупка> покупки = ...;
Тележка общаяТележка = покупки.stream()
        .reduce(new Тележка(), // identity - пустая тележка
                (тележка, штука) -> тележка.положить(штука), // кладём по одной штуке
                Тележка::объединить); // а это combiner, если будем собирать параллельно

Вот и вся магия, блядь. Кажется сложно, но если вникнуть — проще пареной репы. Главное — не пытайся сразу всё понять, а то мозг ебнешь, как Герасим ту Муму. Потихоньку, сука, сориентируйся.