← Назад к вопросам
Что делает map в Stream API?
1.6 Junior🔥 231 комментариев
#Stream API и функциональное программирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что делает map в Stream API?
Введение в map операцию
map — это одна из самых важных промежуточных операций (intermediate operation) в Stream API, которая преобразует каждый элемент потока в новый элемент, применяя переданную функцию. Это функция один-в-один: для каждого входного элемента выдаётся ровно один выходной элемент.
Синтаксис и сигнатура
// Базовый синтаксис
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
Где:
- T — тип входных элементов
- R — тип выходных элементов (могут быть разные!)
- Function<T, R> — функция преобразования
Простейший пример
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Преобразуем каждое число в его квадрат
List<Integer> squared = numbers.stream()
.map(n -> n * n) // map: 1->1, 2->4, 3->9, 4->16, 5->25
.collect(Collectors.toList());
System.out.println(squared); // [1, 4, 9, 16, 25]
Практический пример: Преобразование объектов
// Исходный класс
public class User {
private Long id;
private String name;
private String email;
// Getters, constructors...
}
// DTO для ответа API
public class UserResponseDTO {
private Long id;
private String name;
public UserResponseDTO(Long id, String name) {
this.id = id;
this.name = name;
}
}
// Использование map для преобразования
List<User> users = getUsersFromDatabase();
List<UserResponseDTO> response = users.stream()
.map(user -> new UserResponseDTO(user.getId(), user.getName()))
.collect(Collectors.toList());
// Или ещё компактнее через method reference
List<UserResponseDTO> response = users.stream()
.map(user -> new UserResponseDTO(user.getId(), user.getName()))
.collect(Collectors.toList());
map vs forEach — в чём разница?
List<String> words = Arrays.asList("hello", "world", "java");
// ❌ map без терминальной операции НЕ ВЫПОЛНЯЕТСЯ
words.stream()
.map(String::toUpperCase); // НИЧЕГО не произойдёт!
// ✅ map с collect выполняет преобразование
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList()); // ["HELLO", "WORLD", "JAVA"]
// ✅ forEach выполняется и что-то делает
words.stream()
.forEach(word -> System.out.println(word.toUpperCase())); // Выведет в консоль
Цепочка map операций (композиция)
List<String> words = Arrays.asList("hello", "world", "java");
// Применяем несколько трансформаций подряд
List<Integer> result = words.stream()
.map(String::toUpperCase) // map: "hello" -> "HELLO"
.map(String::length) // map: "HELLO" -> 5
.map(len -> len * 2) // map: 5 -> 10
.collect(Collectors.toList()); // [10, 10, 10]
System.out.println(result); // [10, 10, 10]
Примитивные Stream типы
Для оптимизации Java предоставляет специализированные методы:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// mapToInt — преобразование в IntStream
int sum = numbers.stream()
.mapToInt(Integer::intValue) // Integer -> int
.sum(); // 15
// mapToLong
long product = numbers.stream()
.mapToLong(n -> (long) n)
.reduce(1, (a, b) -> a * b);
// mapToDouble
List<Double> squared = numbers.stream()
.mapToDouble(n -> Math.sqrt(n))
.boxed() // DoubleStream -> Stream<Double>
.collect(Collectors.toList());
Ленивое вычисление (Lazy Evaluation)
Одна из важнейших особенностей map — это ленивое вычисление:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// map НЕ выполняется, пока не будет вызвана терминальная операция
Stream<Integer> stream = numbers.stream()
.map(n -> {
System.out.println("Transforming: " + n);
return n * 2;
});
System.out.println("Stream создан, но map ещё не выполнен!");
// Вот здесь начнёт выполняться map
List<Integer> result = stream.collect(Collectors.toList());
// Output:
// Stream создан, но map ещё не выполнен!
// Transforming: 1
// Transforming: 2
// Transforming: 3
// Transforming: 4
// Transforming: 5
flatMap — when you need to flatten
Если преобразование возвращает Stream, используй flatMap:
List<String> sentences = Arrays.asList(
"Hello world",
"Java streams",
"Are powerful"
);
// ❌ map вернёт Stream<Stream<String>>
Stream<Stream<String>> problematic = sentences.stream()
.map(sentence -> Arrays.stream(sentence.split(" ")));
// ✅ flatMap автоматически "развернёт" вложенные потоки
List<String> words = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.collect(Collectors.toList());
System.out.println(words);
// ["Hello", "world", "Java", "streams", "Are", "powerful"]
Сложный пример: Трансформация коллекций
public class Order {
private Long id;
private List<OrderItem> items;
// Getters...
}
public class OrderItem {
private String productName;
private BigDecimal price;
// Getters...
}
// Получить все названия товаров из всех заказов
List<Order> orders = getOrders();
List<String> productNames = orders.stream()
.flatMap(order -> order.getItems().stream()) // Развернуть все items
.map(OrderItem::getProductName) // Получить название
.distinct() // Убрать дубликаты
.sorted() // Отсортировать
.collect(Collectors.toList());
Производительность
List<Integer> numbers = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
// Вариант 1: Несколько map операций
long start1 = System.currentTimeMillis();
List<Integer> result1 = numbers.stream()
.map(n -> n * 2) // 1 млн операций
.map(n -> n + 1) // 1 млн операций
.filter(n -> n % 2 == 0) // 1 млн операций
.collect(Collectors.toList());
long time1 = System.currentTimeMillis() - start1;
// Вариант 2: Одна комбинированная операция
long start2 = System.currentTimeMillis();
List<Integer> result2 = numbers.stream()
.filter(n -> (n * 2 + 1) % 2 == 0) // 1 млн операций
.map(n -> n * 2 + 1)
.collect(Collectors.toList());
long time2 = System.currentTimeMillis() - start2;
// Совет: первый вариант может быть медленнее из-за избыточных обходов
// Но Java JIT может оптимизировать это
Сравнение с альтернативами
| Подход | Код | Особенность |
|---|---|---|
| map | .map(x -> x * 2) | Функциональный, читаемый |
| forEach | .forEach(x -> list.add(x * 2)) | Императивный, требует изменяемого списка |
| for loop | for (Integer x : list) { ... } | Традиционный, требует больше кода |
| for-i loop | for (int i = 0; i < list.size(); i++) | Допотопный, ошибкоподвержен |
Ключевые моменты
- Промежуточная операция — НЕ выполняется без терминальной операции
- Функциональная трансформация — каждый элемент преобразуется функцией
- Ленивое вычисление — элементы обрабатываются по мере необходимости
- Композируемость — можно цеплять несколько map подряд
- Тип-безопасность — компилятор проверяет типы
- Производительность — часто быстрее чем традиционные циклы благодаря оптимизациям JIT
map — это фундаментальная операция функционального программирования в Java, и её глубокое понимание критично для эффективного использования Stream API.