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

Что произойдет при передаче листов в flatMap?

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

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

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

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

flatMap с листами (Stream API)

flatMap — это функция в Stream API, которая "выравнивает" (flatten) вложенные структуры данных. Когда передаёшь листы в flatMap, происходит трансформация вложенной коллекции в единый плоский поток элементов. Это один из самых мощных инструментов в Stream API.

Что такое flatMap

flatMap = map + flatten

  1. map — трансформирует каждый элемент
  2. flatten — "разворачивает" вложенные структуры

Простой пример

// Исходные данные: список листов
List<List<Integer>> nestedList = Arrays.asList(
    Arrays.asList(1, 2, 3),
    Arrays.asList(4, 5),
    Arrays.asList(6, 7, 8)
);

// Без flatMap (ошибка — тип не совпадает)
// List<Integer> result = nestedList.stream()
//     .map(list -> list)  // Returns Stream<List<Integer>>
//     .collect(Collectors.toList());

// С flatMap (правильно)
List<Integer> result = nestedList.stream()
    .flatMap(List::stream)  // Flatten List<Integer> в Integer
    .collect(Collectors.toList());

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

Как это работает внутри

Шаг за шагом:

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

input.stream()
    // Шаг 1: map трансформирует каждый List<Integer> в Stream<Integer>
    // [Stream(1,2,3), Stream(4,5), Stream(6,7,8)]
    
    .flatMap(List::stream)
    // Шаг 2: flatten разворачивает все потоки
    // [1, 2, 3, 4, 5, 6, 7, 8] — один плоский поток
    
    .forEach(System.out::println);

Сравнение map vs flatMap

List<List<String>> words = Arrays.asList(
    Arrays.asList("hello", "world"),
    Arrays.asList("java", "stream")
);

// 1. map — вложенная структура
List<Stream<String>> withMap = words.stream()
    .map(List::stream)
    .collect(Collectors.toList());
// Result: [Stream(hello, world), Stream(java, stream)]
// Это Stream<Stream<String>> — не то, что нам нужно!

// 2. flatMap — плоская структура
List<String> withFlatMap = words.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());
// Result: [hello, world, java, stream]
// Это List<String> — идеально!

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

Пример 1: Распаковка списков пользователей

class User {
    private String name;
    private List<String> emails;
    
    public User(String name, List<String> emails) {
        this.name = name;
        this.emails = emails;
    }
    
    public List<String> getEmails() {
        return emails;
    }
}

public class FlatMapExample1 {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", Arrays.asList("alice@example.com", "alice.work@company.com")),
            new User("Bob", Arrays.asList("bob@example.com")),
            new User("Charlie", Arrays.asList("charlie@example.com", "charlie.dev@github.com"))
        );
        
        // Получить все email адреса всех пользователей
        List<String> allEmails = users.stream()
            .flatMap(user -> user.getEmails().stream())
            .collect(Collectors.toList());
        
        System.out.println(allEmails);
        // [alice@example.com, alice.work@company.com, bob@example.com, charlie@example.com, charlie.dev@github.com]
    }
}

Пример 2: Декартово произведение

public class CartesianProduct {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3);
        List<String> letters = Arrays.asList("a", "b");
        
        // Создать все комбинации (декартово произведение)
        List<String> combinations = numbers.stream()
            .flatMap(n -> letters.stream()
                .map(l -> n + l)  // Вложенный map
            )
            .collect(Collectors.toList());
        
        System.out.println(combinations);
        // [1a, 1b, 2a, 2b, 3a, 3b]
    }
}

Пример 3: Извлечение слов из предложений

public class WordExtraction {
    public static void main(String[] args) {
        List<String> sentences = Arrays.asList(
            "Hello world",
            "Java is awesome",
            "flatMap is powerful"
        );
        
        // Получить все слова
        List<String> words = sentences.stream()
            .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
            .collect(Collectors.toList());
        
        System.out.println(words);
        // [Hello, world, Java, is, awesome, flatMap, is, powerful]
        
        // Или с фильтром
        List<String> longWords = sentences.stream()
            .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
            .filter(word -> word.length() > 3)
            .collect(Collectors.toList());
        
        System.out.println(longWords);
        // [Hello, world, Java, awesome, flatMap, powerful]
    }
}

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

public class OptionalFlatMap {
    public static void main(String[] args) {
        List<Optional<String>> optionals = Arrays.asList(
            Optional.of("hello"),
            Optional.empty(),
            Optional.of("world"),
            Optional.empty()
        );
        
        // Без flatMap — получим Stream<Optional<String>>
        // С flatMap — получим Stream<String>, пустые Optional исчезнут
        List<String> values = optionals.stream()
            .flatMap(Optional::stream)  // Java 9+
            .collect(Collectors.toList());
        
        System.out.println(values);  // [hello, world]
    }
}

Сложный пример: вложенный flatMap

class Department {
    private String name;
    private List<Employee> employees;
    
    public List<Employee> getEmployees() { return employees; }
}

class Employee {
    private String name;
    private List<String> skills;
    
    public List<String> getSkills() { return skills; }
}

public class NestedFlatMap {
    public static void main(String[] args) {
        List<Department> departments = Arrays.asList(
            new Department("Backend", Arrays.asList(
                new Employee("Alice", Arrays.asList("Java", "SQL", "Spring")),
                new Employee("Bob", Arrays.asList("Java", "Kotlin"))
            )),
            new Department("Frontend", Arrays.asList(
                new Employee("Charlie", Arrays.asList("React", "TypeScript"))
            ))
        );
        
        // Получить ВСЕ навыки ВСЕ сотрудников ВСЕ отделов
        List<String> allSkills = departments.stream()
            .flatMap(dept -> dept.getEmployees().stream())
            .flatMap(emp -> emp.getSkills().stream())
            .distinct()
            .sorted()
            .collect(Collectors.toList());
        
        System.out.println(allSkills);
        // [Java, Kotlin, React, SQL, Spring, TypeScript]
    }
}

Производительность

flatMap создаёт промежуточные потоки, что может быть не очень эффективно для больших объёмов данных:

// Менее эффективно для больших данных
List<Integer> result = largeListOfLists.stream()
    .flatMap(List::stream)
    .filter(n -> n > 10)
    .collect(Collectors.toList());

// Более эффективно
List<Integer> result = new ArrayList<>();
for (List<Integer> list : largeListOfLists) {
    for (Integer n : list) {
        if (n > 10) {
            result.add(n);
        }
    }
}

Однако для читаемости Stream API часто стоит этой цены.

Когда использовать flatMap

Используй когда:

  • У тебя есть вложенные коллекции
  • Нужно преобразовать их в плоский поток
  • Хочешь сохранить функциональный стиль кода

Не используй когда:

  • Нужна максимальная производительность на очень больших данных
  • Логика слишком сложная — лучше написать обычный цикл

Вывод

При передаче листов в flatMap:

  1. Каждый лист преобразуется в Stream через function
  2. Все потоки "разворачиваются" в один плоский поток
  3. Результат — единая последовательность элементов из всех листов

Это очень мощный инструмент для работы со вложенными структурами данных. flatMap — это одна из самых важных операций в Stream API, которую должен отлично понимать каждый Java разработчик.