Що робить операція reduce()?
Покроковий розбір для [1, 2, 3, 4, 5]:
🟢 Junior Level
reduce() — це terminal-операція, яка згортає стрим в одне значення.
Три варіанти:
// 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()— terminal-операція, що згортає стрим в одне значення- Три сигнатури: (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 операціях]]