Що робить операція map()?
Приймає Function
🟢 Junior Level
map(Function) — це проміжна операція, яка перетворює кожен елемент стріма в інший елемент (“один до одного”).
Приймає Function<T, R> — функціональний інтерфейс з методом R apply(T t).
List<String> words = List.of("hello", "world");
// Перетворюємо слова на їх довжину
List<Integer> lengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
// Результат: [5, 5]
// Перетворення у верхній регістр
List<String> upper = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Результат: ["HELLO", "WORLD"]
Важливо: map завжди повертає один об’єкт для кожного вхідного елемента.
🟡 Middle Level
Внутрішня реалізація
map вбудовується в ланцюжок Sink:
public void accept(T t) {
downstream.accept(mapper.apply(t));
}
Завдяки лінивості, якщо результат map не використовується terminal операцією, функція перетворення ніколи не буде викликана.
Уникайте Wrapper-ада (Автобоксинг)
// ПОГАНО — створює мільйони об'єктів Integer у купі
list.stream().map(s -> s.length()).reduce(0, Integer::sum);
// ДОБРЕ — працює з примітивами у стеку
list.stream().mapToInt(String::length).sum();
Primitive Streams (IntStream, LongStream, DoubleStream) радикально знижують навантаження на GC та споживання пам’яті.
IntStream/LongStream/DoubleStream зберігають примітиви напряму, без обгорток (Integer, Long). Для 1 млн елементів: Stream
Map vs FlatMap
map: повертає один об’єкт для кожного вхідногоflatMap: повертає стрім об’єктів (від 0 до нескінченності), які “сплющуються” в один потік
Чистота функцій (Purity)
Функція в map рекомендується чистою (без побічних ефектів). На практиці impure-функції працюють, але з ризиками: в parallelStream результат буде непередбачуваним.
- Не змінювати вхідний об’єкт (імутабельність)
- Не мати побічних ефектів (не писати в БД, не змінювати статичні поля)
- Повертати один і той самий результат для тих самих вхідних даних
Коли НЕ використовувати map
- Вам не потрібен результат перетворення — використовуйте forEach (для дій) або peek (для налагодження)
- Фільтрація + трансформація в одну — іноді краще один цикл з if + transform
- Робота з побічними ефектами — map не для I/O, використовуйте forEach
🔴 Senior Level
Method References та JIT оптимізація
Використовуйте String::toUpperCase замість s -> s.toUpperCase(). Це не лише чистіше, але й допомагає JIT-компілятору краще оптимізувати виклики через invokedynamic.
Null Handling
Якщо функція мапінгу повертає null, стрім піде далі з null-елементом. Це часто призводить до NPE в наступних ланках:
// Безпечний підхід
stream.map(User::getEmail)
.filter(Objects::nonNull)
.map(String::toLowerCase)
Checked Exceptions
Лямбди в map не можуть кинути checked виключення. Доводиться огортати в RuntimeException або використовувати допоміжні інтерфейси:
// Обгортка checked виключення
stream.map(s -> {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
})
Object Allocation та Highload
Якщо функція в map створює важкі об’єкти, розгляньте можливість перевикористання об’єктів (Object Pooling), хоча в стрімах це складно реалізувати.
Діагностика
- Type Changes: Стежте за тим, як змінюється тип стріма в ланцюжку — IntelliJ IDEA підсвічує типи
- Side Effect Detection: Якщо
mapміститьSystem.out.printlnабоlog.info— це ознака поганого дизайну. Для логування використовуйтеpeek().
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
map(Function)— проміжна операція, перетворення «один до одного»- Приймає
Function<T, R>, повертаєStream<R> - Primitive Streams (mapToInt, mapToLong) уникають автобоксингу та економлять пам’ять
- Функція мапінгу має бути чистою: без побічних ефектів, детермінована
- Method References (
String::toUpperCase) пріоритетніші за лямбди — чистіше і допомагає JIT - Якщо mapper повертає null — стрім піде далі з null-елементом (ризик NPE)
Часті уточнюючі запитання:
- map vs flatMap? — map повертає один об’єкт, flatMap — стрім об’єктів, який «сплющується»
- Чому mapToInt кращий за map? — IntStream зберігає примітиви в масиві, Stream
створює об'єкти в купі - Як обробляти checked exceptions у лямбді? — Обгорнути в RuntimeException або використати допоміжні інтерфейси
- Чи можна використовувати map для I/O? — Ні, map не для побічних ефектів, використовуйте forEach
Червоні прапорці (НЕ говорити):
- «map може повернути кілька об’єктів для одного елемента» — це flatMap
- «Автобоксинг не впливає на продуктивність» — для мільйонів елементів різниця в 6x за пам’яттю
- «Функція в map може змінювати зовнішні змінні» — це порушує чистоту і ламає parallelStream
- «map і forEach взаємозамінні» — map трансформує, forEach виконує дію
Пов’язані теми:
- [[7. Що робить операція flatMap()]]
- [[8. В чому різниця між map() та flatMap()]]
- [[3. Що робить операція filter()]]
- [[5. Що робить операція collect()]]