Что произойдет при передаче листов в flatMap?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
flatMap с листами (Stream API)
flatMap — это функция в Stream API, которая "выравнивает" (flatten) вложенные структуры данных. Когда передаёшь листы в flatMap, происходит трансформация вложенной коллекции в единый плоский поток элементов. Это один из самых мощных инструментов в Stream API.
Что такое flatMap
flatMap = map + flatten
- map — трансформирует каждый элемент
- 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:
- Каждый лист преобразуется в Stream через function
- Все потоки "разворачиваются" в один плоский поток
- Результат — единая последовательность элементов из всех листов
Это очень мощный инструмент для работы со вложенными структурами данных. flatMap — это одна из самых важных операций в Stream API, которую должен отлично понимать каждый Java разработчик.