← Назад к вопросам

Что делает 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 loopfor (Integer x : list) { ... }Традиционный, требует больше кода
for-i loopfor (int i = 0; i < list.size(); i++)Допотопный, ошибкоподвержен

Ключевые моменты

  1. Промежуточная операция — НЕ выполняется без терминальной операции
  2. Функциональная трансформация — каждый элемент преобразуется функцией
  3. Ленивое вычисление — элементы обрабатываются по мере необходимости
  4. Композируемость — можно цеплять несколько map подряд
  5. Тип-безопасность — компилятор проверяет типы
  6. Производительность — часто быстрее чем традиционные циклы благодаря оптимизациям JIT

map — это фундаментальная операция функционального программирования в Java, и её глубокое понимание критично для эффективного использования Stream API.

Что делает map в Stream API? | PrepBro