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

Чем отличается метод map() от flatMap() в Stream API?

2.0 Middle🔥 141 комментариев
#Stream API и функциональное программирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между map() и flatMap() в Stream API

map() и flatMap() — это два промежуточных операции в Stream API для преобразования данных. Они похожи, но решают разные задачи.

Сравнительная таблица

Параметрmap()flatMap()
Преобразованиеодин элемент → один элементодин элемент → несколько элементов
Тип результатаStream<T>Stream<R> (объединение нескольких потоков)
СтруктураСохраняет структуру"Развертывает" вложенные потоки
ИспользованиеТрансформацияСглаживание (flattening)
Количество выходов1:11:N

Метод map()

map() — это метод для преобразования каждого элемента потока в новое значение (обычно другого типа).

public interface Stream<T> {
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
}

Каждый элемент преобразуется ровно в один элемент:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Преобразуем каждое число в его квадрат
List<Integer> squares = numbers.stream()
    .map(n -> n * n)
    .collect(Collectors.toList());

System.out.println(squares);  // [1, 4, 9, 16, 25]

// Преобразуем числа в строки
List<String> strings = numbers.stream()
    .map(n -> "число: " + n)
    .collect(Collectors.toList());

System.out.println(strings);
// [число: 1, число: 2, число: 3, число: 4, число: 5]

Практический пример map()

public class User {
    private String name;
    private int age;
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
}

public class Main {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Иван", 25),
            new User("Мария", 30),
            new User("Петр", 28)
        );
        
        // Преобразуем User → String (имена)
        List<String> names = users.stream()
            .map(User::getName)
            .collect(Collectors.toList());
        
        System.out.println(names);  // [Иван, Мария, Петр]
        
        // Преобразуем User → Integer (возраст)
        List<Integer> ages = users.stream()
            .map(User::getAge)
            .collect(Collectors.toList());
        
        System.out.println(ages);  // [25, 30, 28]
    }
}

Метод flatMap()

flatMap() — это метод для преобразования каждого элемента в поток элементов, а затем объединения всех потоков в один. Это "развертывание" вложенной структуры.

public interface Stream<T> {
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
}

Каждый элемент может быть преобразован в несколько элементов:

List<List<Integer>> matrix = Arrays.asList(
    Arrays.asList(1, 2, 3),
    Arrays.asList(4, 5, 6),
    Arrays.asList(7, 8, 9)
);

// map() вернет Stream<List<Integer>>
List<List<Integer>> result1 = matrix.stream()
    .map(list -> list)  // нет преобразования
    .collect(Collectors.toList());

System.out.println(result1);  // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

// flatMap() развернет все списки в один поток
List<Integer> result2 = matrix.stream()
    .flatMap(List::stream)  // развертывание
    .collect(Collectors.toList());

System.out.println(result2);  // [1, 2, 3, 4, 5, 6, 7, 8, 9]

Визуализация:

map():
Input:  [[1, 2], [3, 4]]
        ↓
Output: [[1, 2], [3, 4]]

flatMap():
Input:  [[1, 2], [3, 4]]
        ↓
Output: [1, 2, 3, 4]

Практические примеры

Пример 1: Распределение задач по отделам

public class Department {
    private String name;
    private List<String> tasks;
    
    public Department(String name, List<String> tasks) {
        this.name = name;
        this.tasks = tasks;
    }
    
    public List<String> getTasks() { return tasks; }
}

public class Main {
    public static void main(String[] args) {
        List<Department> departments = Arrays.asList(
            new Department("Frontend", 
                Arrays.asList("Верстка", "JavaScript", "Дизайн")),
            new Department("Backend", 
                Arrays.asList("API", "Database", "Security")),
            new Department("DevOps", 
                Arrays.asList("Deployment", "Monitoring", "Infrastructure"))
        );
        
        // map() вернет Stream<List<String>> (вложенная структура)
        departments.stream()
            .map(Department::getTasks)
            .forEach(System.out::println);
        // Output:
        // [Верстка, JavaScript, Дизайн]
        // [API, Database, Security]
        // [Deployment, Monitoring, Infrastructure]
        
        // flatMap() развернет всё в один список задач
        List<String> allTasks = departments.stream()
            .flatMap(dept -> dept.getTasks().stream())
            .collect(Collectors.toList());
        
        System.out.println(allTasks);
        // [Верстка, JavaScript, Дизайн, API, Database, Security, ...]
    }
}

Пример 2: Преобразование строк в слова

public class Main {
    public static void main(String[] args) {
        List<String> sentences = Arrays.asList(
            "Hello world",
            "Java Stream",
            "FlatMap example"
        );
        
        // map() дает список списков
        sentences.stream()
            .map(s -> s.split(" "))
            .forEach(arr -> System.out.println(Arrays.toString(arr)));
        // Output:
        // [Hello, world]
        // [Java, Stream]
        // [FlatMap, example]
        
        // flatMap() дает один список всех слов
        List<String> allWords = sentences.stream()
            .flatMap(s -> Arrays.stream(s.split(" ")))
            .collect(Collectors.toList());
        
        System.out.println(allWords);
        // [Hello, world, Java, Stream, FlatMap, example]
    }
}

Пример 3: Работа с Optional

public class Main {
    public static void main(String[] args) {
        List<String> emails = Arrays.asList(
            "john@example.com",
            null,
            "jane@example.com",
            null,
            "bob@example.com"
        );
        
        // map() вернет Stream<Optional<String>>
        List<Optional<String>> result1 = emails.stream()
            .map(email -> Optional.ofNullable(email))
            .collect(Collectors.toList());
        
        System.out.println(result1.size());  // 5 (включая Optional.empty())
        
        // flatMap() развернет Optional в Stream и отфильтрует пусто
        List<String> result2 = emails.stream()
            .flatMap(email -> Optional.ofNullable(email).stream())
            .collect(Collectors.toList());
        
        System.out.println(result2.size());  // 3 (только валидные email)
        System.out.println(result2);
        // [john@example.com, jane@example.com, bob@example.com]
    }
}

Сравнение в действии

List<List<Integer>> matrix = Arrays.asList(
    Arrays.asList(1, 2),
    Arrays.asList(3, 4),
    Arrays.asList(5, 6)
);

System.out.println("=== map() ===");
List<Integer> sum1 = matrix.stream()
    .map(list -> list.stream()
        .mapToInt(Integer::intValue)
        .sum())
    .collect(Collectors.toList());
System.out.println(sum1);  // [3, 7, 11] (суммы подсписков)

System.out.println("=== flatMap() ===");
List<Integer> allElements = matrix.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());
System.out.println(allElements);  // [1, 2, 3, 4, 5, 6] (все элементы)

int total = matrix.stream()
    .flatMap(List::stream)
    .mapToInt(Integer::intValue)
    .sum();
System.out.println(total);  // 21 (сумма всех элементов)

Сложный пример: flatMap с фильтрацией

public class Order {
    private int id;
    private List<Item> items;
    
    public Order(int id, List<Item> items) {
        this.id = id;
        this.items = items;
    }
    
    public List<Item> getItems() { return items; }
}

public class Item {
    private String name;
    private double price;
    
    public Item(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    public String getName() { return name; }
    public double getPrice() { return price; }
}

public class Main {
    public static void main(String[] args) {
        List<Order> orders = Arrays.asList(
            new Order(1, Arrays.asList(
                new Item("Laptop", 1000),
                new Item("Mouse", 30)
            )),
            new Order(2, Arrays.asList(
                new Item("Book", 20),
                new Item("Pen", 5)
            )),
            new Order(3, Arrays.asList(
                new Item("Phone", 800),
                new Item("Charger", 50)
            ))
        );
        
        // Получить все товары стоимостью > 100
        List<String> expensiveItems = orders.stream()
            .flatMap(order -> order.getItems().stream())
            .filter(item -> item.getPrice() > 100)
            .map(Item::getName)
            .collect(Collectors.toList());
        
        System.out.println(expensiveItems);  // [Laptop, Phone]
    }
}

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

  • map() преобразует элемент 1:1
  • flatMap() преобразует элемент 1:N и объединяет результаты
  • flatMap() полезен для развертывания вложенных структур
  • flatMap() с Optional — отличный способ фильтровать null значения
  • flatMap() часто медленнее, чем map(), так как создает дополнительные потоки
Чем отличается метод map() от flatMap() в Stream API? | PrepBro