В чём разница между map() и flatMap()?
Главное отличие — что возвращает функция-маппер:
🟢 Junior Level
Главное отличие — что возвращает функция-маппер:
| Критерий | map() |
flatMap() |
|---|---|---|
| Когда | Преобразование 1→1 | Преобразование 1→N + объединение |
| Возврат маппера | R (один объект) |
Stream<R> (стрим объектов) |
| Соответствие | 1-к-1 | 1-ко-многим |
| Вложенность | Нет | Схлопывает вложенные стримы |
map(Function<T, R>):
- Функция возвращает один объект типа
R - Соответствие 1-к-1
List<String> words = List.of("hello", "world"); words.stream().map(String::length) // [5, 5]
flatMap(Function<T, Stream<R>>):
- Функция возвращает стрим объектов типа
R - Соответствие 1-ко-многим (0, 1 или много)
List<List<String>> nested = List.of(List.of("a", "b"), List.of("c")); nested.stream().flatMap(Collection::stream) // ["a", "b", "c"]
Простое правило: Если у вас коллекция внутри элемента — flatMap, если простое преобразование — map.
🟡 Middle Level
Сигнатуры
// map — принимает T, возвращает R
<R> Stream<R> map(Function<T, R> mapper)
// flatMap — принимает T, возвращает Stream<R>
<R> Stream<R> flatMap(Function<T, Stream<R>> mapper)
Когда выбирать map
- Трансформация данных (
User -> UserDTO) - Извлечение поля (
User -> User.getEmail()) - Тип меняется, но структура линейная
Когда выбирать flatMap
- Объект содержит коллекцию (
Order -> Stream<OrderItem>) - Внешние источники, возвращающие стримы
- Фильтрация + трансформация одновременно (возврат
Stream.empty())
Optional: особенная разница
Optional<User> user = ...;
// map вернёт Optional<Optional<String>> — вложенность!
Optional<Optional<String>> bad = user.map(u -> u.getEmailAsync());
// flatMap вернёт Optional<String> — правильно
Optional<String> good = user.flatMap(u -> u.getEmailAsync());
В Optional flatMap “схлопывает” вложенность.
Когда что НЕ использовать
- НЕ flatMap, если вам нужно просто преобразовать элемент — используйте
map() - НЕ map, если элемент превращается в коллекцию и вы хотите один плоский результат —
map()дастStream<List<T>>, а вам нуженStream<T> - НЕ оба, если вам просто нужно отфильтровать —
filter()читаемее
🔴 Senior Level
Производительность
Memory Overhead: flatMap дороже — для каждого элемента создается новый объект Stream. При миллионах элементов это создает нагрузку на Young Gen.
Short-Circuiting: flatMap поддерживает короткое замыкание. Если после него стоит .findFirst(), выполнение прервется после первого элемента из первого вложенного стрима.
Parallelism: flatMap в параллельных стримах менее эффективен — splitting становится менее предсказуемым из-за переменного количества элементов на выходе.
Null Handling
- В
map: возвратnullсоздает стрим сnull-элементом - В
flatMap: возвратnullвместо стрима →NullPointerException
Infinite Streams
flatMap может объединять бесконечные стримы, но терминальная операция не завершится (если нет короткого замыкания).
Диагностика
- Type Checking: Если видите
Stream<List<String>>— скорее всего забылиflatMap - Testing: Обязательно тестируйте случаи когда маппер возвращает пустой стрим и стрим с одним
null-элементом
🎯 Шпаргалка для интервью
Обязательно знать:
map(Function<T, R>)— возврат одного объекта R, соответствие 1-к-1flatMap(Function<T, Stream<R>>)— возврат стрима объектов, схлопывание вложенности, 1-ко-многим- map для трансформации данных (User → UserDTO), flatMap для извлечения коллекций (Order → Stream
) - В Optional: map может создать
Optional<Optional<T>>, flatMap всегда возвращает плоскийOptional<T> - flatMap дороже по памяти — создаёт новый Stream для каждого элемента
- Оба поддерживают short-circuiting: findFirst() прервёт выполнение после первого элемента
Частые уточняющие вопросы:
- Когда map даст Stream<List
>? — Когда маппер извлекает коллекцию из элемента, а нужен Stream → используйте flatMap - null в map vs flatMap? — map пропустит null дальше, flatMap бросит NullPointerException
- Когда flatMap медленнее? — При миллионах элементов из-за аллокации Stream-объектов
- Можно ли flatMap заменить filter? — Да, возвратом Stream.empty() для отфильтрованных, но filter читаемее
Красные флаги (НЕ говорить):
- «map и flatMap взаимозаменяемы» — нет, разные сигнатуры и семантика
- «flatMap быстрее map» — нет, flatMap всегда дороже из-за создания Stream-объектов
- «map может вернуть 0 элементов» — нет, map всегда возвращает 1 объект (или null)
- «flatMap гарантирует порядок в параллельных стримах» — нет, splitting менее предсказуем
Связанные темы:
- [[4. Что делает операция map()]]
- [[7. Что делает операция flatMap()]]
- [[9. Что такое параллельные стримы]]
- [[2. В чём разница между intermediate и terminal операциями]]