Что делает операция reduce()?
Пошаговый разбор для [1, 2, 3, 4, 5]:
🟢 Junior Level
reduce() — это терминальная операция, которая сворачивает стрим в одно значение.
Три варианта:
// 1. Без начального значения — возвращает Optional
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b);
// 2. С начальным значением
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
// 3. С изменением типа
int lengthSum = strings.stream()
.reduce(0, (total, str) -> total + str.length(), Integer::sum);
Простой пример:
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
// Результат: 15
Пошаговый разбор для [1, 2, 3, 4, 5]:
- Шаг 1: a=0 (identity), b=1 → результат 1
- Шаг 2: a=1 (накопленное), b=2 → результат 3
- Шаг 3: a=3, b=3 → результат 6
- Шаг 4: a=6, b=4 → результат 10
- Шаг 5: a=10, b=5 → результат 15
🟡 Middle Level
Математические требования
Для корректной работы reduce (особенно в параллельном режиме):
- Identity:
identity— «нейтральный элемент»:accumulator.apply(identity, x) = x. Для сложения identity = 0 (0 + x = x). Для умножения identity = 1 (1 × x = x). Если взять identity = 1 для сложения — результат будет завышен на количество элементов! - Associativity:
(a op b) op c == a op (b op c) - Commutativity: Для параллельной обработки желательно, чтобы порядок не влиял на результат
Reduce vs Collect
Это “золотой вопрос” интервью:
reduce: Создает новый объект на каждом шаге. Хорош для чисел, строк, иммутабельных объектовcollect: Модифицирует существующий контейнер. Эффективнее для коллекций
// ПЛОХО — создает миллионы строк O(n²)
stream.reduce("", String::concat)
// ХОРОШО — использует один StringBuilder O(n)
stream.collect(Collectors.joining())
Боксинг
При использовании reduce на Integer/Long — автобоксинг. Для Highload предпочитайте примитивные стримы: IntStream.sum(), LongStream.max().
🔴 Senior Level
Третья сигнатура reduce
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner)
Combiner критически важен для parallelStream() — учит стрим объединять результаты из разных потоков.
Identity Mutation
Никогда не меняйте объект identity. Он используется многократно в параллельных ветках. Если измените — аффектите все параллельные вычисления.
Edge Cases
- Parallel Combiner: Несовместимый комбинер → неверный результат без исключений
- Null Handling: Аккумулятор, возвращающий
null→ NPE - Empty Streams: Версия с одним аргументом возвращает
Optional— не делайте.get()без проверки
Диагностика
Используйте IntelliJ Stream Debugger — визуально показывает, как accumulator объединяет элементы на каждом шаге.
🎯 Шпаргалка для интервью
Обязательно знать:
reduce()— терминальная операция, сворачивающая стрим в одно значение- Три сигнатуры: (1) с
BinaryOperator→Optional, (2) сidentity+BinaryOperator, (3) сidentity+accumulator+combiner(для параллелизма и смены типа) - Математические требования: Identity (
identity op x = x), Associativity ((a op b) op c = a op (b op c)), желательна Commutativity reduce()создаёт новый объект на каждом шаге (immutable reduction); для коллекций используйтеcollect()- Для конкатенации строк:
reduce("", String::concat)— O(n²),collect(Collectors.joining())— O(n) - Identity mutation — грубая ошибка: объект identity используется многократно в параллельных ветках
- На
parallelStream()обязателен корректныйcombiner— иначе неверный результат без исключений
Частые уточняющие вопросы:
- Почему reduce с identity = 1 для сложения даст неверный результат? — identity должен быть нейтральным элементом: 0 для сложения, 1 для умножения. Иначе результат завышен на число элементов
- Чем reduce отличается от collect? — reduce создаёт новый объект на каждом шаге (immutable), collect модифицирует один контейнер (mutable). reduce для чисел/строк, collect для коллекций
- Зачем третий параметр combiner в reduce? — Объединяет результаты из разных потоков в
parallelStream(); без него параллельный режим некорректен - Что вернёт reduce на пустом стриме? — Версия без identity →
Optional.empty(). Версия с identity → значение identity
Красные флаги (НЕ говорить):
- «reduce можно использовать для сборки List» — технически можно, но сломает параллельный режим (мутабельный identity в ветках)
- «Порядок в reduce всегда гарантирован» — в параллельном режиме порядок зависит от combiner
- «Identity можно менять внутри аккумулятора» — это сломает все параллельные вычисления
- «reduce и collect взаимозаменяемы» — collect эффективнее для коллекций O(n) vs O(n²) у reduce
Связанные темы:
- [[В чём разница между reduce() и collect()]]
- [[Что делает операция collect()]]
- [[Какие потенциальные проблемы могут быть с параллельными стримами]]
- [[Можно ли изменять состояние внешних переменных в Stream операциях]]