В чём разница между 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]]
- [[Какие потенциальные проблемы могут быть с параллельными стримами]]