В чому різниця між reduce() та collect()?
Обидві операції згортають стрим у результат, але роблять це по-різному:
🟢 Junior Level
Обидві операції згортають стрим у результат, але роблять це по-різному:
reduce() — створює новий об’єкт на кожному кроці:
// Підсумовування — кожен крок створює нове число
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
collect() — змінює один і той самий об’єкт:
// Додавання в один і той самий список
List<String> list = strings.stream().collect(Collectors.toList());
Просте правило:
- Числа, рядки →
reduce() - Колекції (List, Set, Map) →
collect()
🟡 Middle Level
Тип редукції
reduce() — Immutable Reduction:
- На кожному кроці створюється новий об’єкт
- Приклад:
(String a, String b) -> a + bстворює новий рядок, копіюючи вміст обох
collect() — Mutable Reduction:
- На кожному кроці модифікується існуючий контейнер
- Приклад:
StringBuilder.append()доповнює вже виділений буфер
Складність алгоритму
При збірці в колекцію:
reduce: O(n²) для конкатенації рядків. Кожна конкатенаціяa + bкопіює всі символи обох рядків. Для n рядків довжиною 1: крок 1 копіює 1 символ, крок 2 — 2… Разом 1+2+3+…+n = n(n+1)/2 = O(n²). reduce технічно може зібрати List:reduce(new ArrayList<>(), (list, el) -> { list.add(el); return list; }, ...). Але це зламає паралельний режим: мутабельний identity використовується у всіх гілках одночасно. Тому — тількиcollect(створює окремий контейнер для кожної гілки).collect: O(n) — просто додає посилання в ArrayList
Decision Matrix
| Задача | Операція |
|---|---|
| Числа, примітиви | reduce() або sum(), count() |
| String конкатенація | collect(joining()) |
| List, Set, Map | Тільки collect() |
| Складні DTO | collect() |
🔴 Senior Level
Паралелізм та Combiner
| Характеристика | reduce() | collect() |
|---|---|---|
| Пам’ять | Висока витрата (проміжні об’єкти) | Низька (контейнери перевикористовуються) |
| Combiner | Об’єднує два значення | Об’єднує два контейнери |
| Оптимізація | Складно через алокації | Characteristics.CONCURRENT дозволяє писати в один контейнер |
Identity Value відмінність
- У
reduce: Початкове значення використовується багаторазово в паралельних гілках - У
collect: Для кожної гілки створюється свій контейнер черезsupplier()
Side Effects
- У
collect()мутація контейнера — основа роботи - У
reduce()мутація вхідних параметрів акумулятора — груба помилка, зламає логіку в паралельному режимі
Діагностика
Profiling: Якщо бачите величезну алокацію char[] при роботі з рядками — використовуєте reduce там, де потрібен collect(joining()).
Unit Testing: Завжди запускайте кастомні колектори на parallelStream() з малим батчем для перевірки combiner.
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
reduce()— immutable reduction: створює новий об’єкт на кожному кроці.collect()— mutable reduction: модифікує один контейнер- Алгоритмічна складність:
reduceдля рядків O(n²) (копіювання символів),collectO(n) (додавання посилання) - Золоте правило: числа/рядки →
reduce(), колекції (List, Set, Map) →collect() - У
reduceidentity використовується багаторазово в паралельних гілках; уcollectдля кожної гілки створюється свій контейнер черезsupplier() collectпідтримуєCharacteristics.CONCURRENT— кілька потоків пишуть в один контейнер,reduce— ніколи- Мутація вхідних параметрів акумулятора у
reduce— груба помилка, зламає паралельний режим - Decision Matrix: примітиви →
reduce/sum/count, String →collect(joining()), List/Set/Map → тількиcollect()
Часті уточнюючі запитання:
- Чому
reduceдля конкатенації рядків — O(n²)? — Кожна конкатенаціяa + bкопіює всі символи обох рядків; сума 1+2+3+…+n = n(n+1)/2 - Чи може reduce зібрати List? — Технічно так, але це зламає паралельний режим (мутабельний identity у гілках). Використовуйте
collect - У чому різниця identity у reduce vs supplier у collect? — identity у reduce — один об’єкт, що перевикористовується у всіх гілках. supplier у collect — створює окремий контейнер для кожної гілки
- Як діагностувати misuse reduce/collect? — Профілювання: величезна алокація
char[]при роботі з рядками = потрібенcollect(joining())замістьreduce
Червоні прапорці (НЕ говорити):
- «reduce і collect — одне й те саме, просто різні імена» — фундаментально різний підхід: immutable vs mutable reduction
- «Можна мутувати identity у reduce для оптимізації» — це зламає паралельний режим і дасть неправильний результат
- «collect не підтримує паралелізм» — підтримує через combiner та CONCURRENT characteristic
- «reduce завжди повільніший за collect» — для чисел та іммутабельних об’єктів різниця непомітна; collect швидше для колекцій
Пов’язані теми:
- [[Що робить операція reduce()]]
- [[Що робить операція collect()]]
- [[Що таке Collector та які є вбудовані Collectors]]
- [[Які потенційні проблеми можуть бути з паралельними стримами]]