В чому різниця між 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<T>>? — Коли маппер вилучає колекцію з елемента, а потрібен 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 операціями]]