Что делает операция 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()]]