flatMap и reduce
flatMap — «разворачивает» вложенные коллекции в плоский стрим. Если map превращает каждый элемент в один другой, то flatMap превращает каждый элемент в несколько (или ни одного) и соединяет всё в один стрим.
// У каждого автора — список книг. Хотим все книги одним списком.
List<Author> authors = List.of(
new Author("Толстой", List.of("Война и мир", "Анна Каренина")),
new Author("Чехов", List.of("Вишнёвый сад", "Три сестры"))
);
// map даёт Stream<List<String>> — список списков ❌
// flatMap разворачивает в Stream<String> — один плоский поток ✅
List<String> allBooks = authors.stream()
.flatMap(author -> author.books().stream())
.toList();
// ["Война и мир", "Анна Каренина", "Вишнёвый сад", "Три сестры"]
reduce — свёртка: превращает весь стрим в одно значение, применяя операцию попарно:
// Сумма всех чисел
int sum = List.of(1, 2, 3, 4, 5).stream()
.reduce(0, Integer::sum); // 0 + 1 + 2 + 3 + 4 + 5 = 15
// ↑ начальное значение (identity)
// Самое длинное слово
Optional<String> longest = List.of("Java", "Stream", "API").stream()
.reduce((a, b) -> a.length() >= b.length() ? a : b);
// Optional — потому что если стрим пуст, результата нет
Копнуть глубже
flatMap vs map — как запомнить. map(f) где f: T → R даёт Stream<R>. Если f: T → List<R>, то map даст Stream<List<R>> — вложенный стрим. flatMap(f) «разворачивает» каждый List<R> и склеивает в один Stream<R>. Любой раз когда видите Stream<List<...>> или Stream<Optional<...>> — скорее всего нужен flatMap.
reduce и его три формы:
// 1. С identity (начальным значением) — всегда возвращает T
T reduce(T identity, BinaryOperator<T> accumulator)
// 2. Без identity — возвращает Optional<T> (стрим может быть пустым)
Optional<T> reduce(BinaryOperator<T> accumulator)
// 3. С combiner — для параллельных стримов где тип аккумулятора отличается
<U> U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner)
Когда reduce, а когда collect. reduce для агрегации в одно значение (сумма, произведение, конкатенация, максимум). collect для сборки в контейнер (список, карту, строку через joining). На практике для стандартных операций часто есть готовый Collector или специализированный метод (count(), sum(), max()), который эффективнее ручного reduce.
// Не обязательно reduce — есть специализированные методы:
stream.mapToInt(Integer::intValue).sum();
stream.mapToInt(Integer::intValue).average();
stream.max(Comparator.naturalOrder());
Частый вопрос на собесе: «Чем отличается map от flatMap?» — map 1→1, flatMap 1→N и разворачивает. Попросят пример с вложенными коллекциями — используйте пример с авторами и книгами.
• что делает reduce и зачем нужно начальное значение (identity) (если дошёл до 2-го слоя).