Что получишь при передаче коллекции объектов в flatMap
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
flatMap: преобразование коллекций в потоки
flatMap — это операция в Stream API, которая преобразует каждый элемент в поток (Stream) и объединяет все потоки в один. Это инструмент для работы с вложенными структурами данных.
Основная концепция
flatMap = "flatten" (распрямить) + "map" (преобразовать)
Она решает проблему: когда каждый элемент порождает коллекцию или поток, и ты хочешь работать со всеми элементами плоской структурой, а не с вложенными коллекциями.
Сигнатура
public <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
Функция mapper преобразует элемент T в Stream<R>, а flatMap объединяет все эти потоки.
Пример 1: Базовое использование
List<List<Integer>> numbers = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
// Без flatMap: вложенная структура
List<Stream<Integer>> streams = numbers.stream()
.map(list -> list.stream()) // Stream<Stream<Integer>>
.collect(Collectors.toList());
// Результат: [Stream[1,2,3], Stream[4,5], Stream[6,7,8,9]]
// С flatMap: одноуровневая структура
List<Integer> flattened = numbers.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
// Результат: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Пример 2: Работа с объектами
Представь класс User с коллекцией Orders:
class User {
private String name;
private List<Order> orders;
public User(String name, List<Order> orders) {
this.name = name;
this.orders = orders;
}
public List<Order> getOrders() { return orders; }
public String getName() { return name; }
}
class Order {
private String id;
private BigDecimal amount;
public Order(String id, BigDecimal amount) {
this.id = id;
this.amount = amount;
}
public BigDecimal getAmount() { return amount; }
}
// Список пользователей
List<User> users = Arrays.asList(
new User("Alice", Arrays.asList(
new Order("O1", new BigDecimal("100")),
new Order("O2", new BigDecimal("200"))
)),
new User("Bob", Arrays.asList(
new Order("O3", new BigDecimal("150"))
)),
new User("Charlie", Arrays.asList(
new Order("O4", new BigDecimal("300")),
new Order("O5", new BigDecimal("250"))
))
);
Теперь проблема: получить все заказы всех пользователей.
❌ Без flatMap (неудобно)
List<Order> allOrders = new ArrayList<>();
for (User user : users) {
allOrders.addAll(user.getOrders());
}
System.out.println(allOrders); // [O1, O2, O3, O4, O5]
✅ С flatMap (элегантно)
List<Order> allOrders = users.stream()
.flatMap(user -> user.getOrders().stream())
.collect(Collectors.toList());
// Результат: [Order{O1}, Order{O2}, Order{O3}, Order{O4}, Order{O5}]
Пример 3: Фильтрация + flatMap
// Получи все заказы сумму > 200
BigDecimal totalAmount = users.stream()
.flatMap(user -> user.getOrders().stream())
.filter(order -> order.getAmount().compareTo(new BigDecimal("200")) > 0)
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println(totalAmount); // 200 + 250 + 300 = 750
Пример 4: Трансформация и flatMap
List<String> words = Arrays.asList("Hello", "World", "Stream");
// Получи все уникальные буквы
List<String> letters = words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.distinct()
.collect(Collectors.toList());
System.out.println(letters);
// [H, e, l, o, W, r, d, S, t, a, m]
Пример 5: Arrays.stream() и flatMap
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Превратить матрицу в одномерный поток
int[] flattened = Arrays.stream(matrix)
.flatMapToInt(row -> Arrays.stream(row))
.toArray();
System.out.println(Arrays.toString(flattened));
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
Пример 6: Optional и flatMap
Special case: когда функция возвращает Optional (может быть пусто):
class Person {
private String name;
private Optional<Address> address; // Может не быть
public Person(String name, Optional<Address> address) {
this.name = name;
this.address = address;
}
public Optional<Address> getAddress() { return address; }
}
class Address {
private String street;
public Address(String street) { this.street = street; }
public String getStreet() { return street; }
}
List<Person> people = Arrays.asList(
new Person("Alice", Optional.of(new Address("Main St"))),
new Person("Bob", Optional.empty()), // Адреса нет
new Person("Charlie", Optional.of(new Address("Oak Ave")))
);
// Получи все адреса (пропускаем пустые Optional)
List<String> addresses = people.stream()
.flatMap(person -> person.getAddress().stream()) // stream() для Optional
.map(Address::getStreet)
.collect(Collectors.toList());
System.out.println(addresses); // [Main St, Oak Ave] (Bob пропущен)
Пример 7: flatMapToInt, flatMapToLong, flatMapToDouble
Для примитивных типов:
List<String> numbers = Arrays.asList("1,2,3", "4,5", "6,7,8");
// Получи сумму всех чисел
int sum = numbers.stream()
.flatMapToInt(str -> Arrays.stream(
Arrays.stream(str.split(","))
.mapToInt(Integer::parseInt)
.toArray()
))
.sum();
System.out.println(sum); // 1+2+3+4+5+6+7+8 = 36
Различие: map vs flatMap
List<List<Integer>> data = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);
// map: преобразует каждый элемент (вложенная структура)
Stream<Stream<Integer>> mapped = data.stream()
.map(list -> list.stream()); // Stream<Stream<Integer>>
System.out.println("map result: Stream<Stream<Integer>>");
// flatMap: преобразует и объединяет (плоская структура)
Stream<Integer> flatMapped = data.stream()
.flatMap(list -> list.stream()); // Stream<Integer>
System.out.println("flatMap result: Stream<Integer>");
// Визуально
// map: [[1, 2], [3, 4]] (вложено)
// flatMap: [1, 2, 3, 4] (плоско)
Производительность: когда flatMap дорогой
// ❌ Избегай: создание пустых потоков
users.stream()
.flatMap(user -> user.getOrders().isEmpty()
? Stream.empty() // Пустой поток
: user.getOrders().stream())
.collect(Collectors.toList());
// ✅ Лучше: фильтруй до flatMap
users.stream()
.filter(user -> !user.getOrders().isEmpty())
.flatMap(user -> user.getOrders().stream())
.collect(Collectors.toList());
Практический пример: REST API
@Service
public class OrderService {
private final UserRepository userRepository;
public List<OrderDTO> getAllOrders() {
return userRepository.findAll().stream()
.flatMap(user -> user.getOrders().stream())
.map(order -> new OrderDTO(
order.getId(),
order.getAmount(),
user.getName()
))
.collect(Collectors.toList());
}
public BigDecimal getTotalRevenue() {
return userRepository.findAll().stream()
.flatMap(user -> user.getOrders().stream())
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
Правила использования flatMap
- Используй flatMap когда каждый элемент порождает коллекцию/поток
- Фильтруй до flatMap когда можешь убрать пустые потоки
- Мемоизируй результаты если функция дорогая (SQL запрос)
- Избегай вложенности — max 2-3 уровня flatMap для читаемости
- Тестируй граничные случаи — пустые коллекции, null значения
Итоговый чеклист
- flatMap раскрывает вложенные структуры в одноуровневый поток
- Каждый элемент преобразуется в Stream и все потоки объединяются
- Optional.stream() — удобный способ работать с Optional в flatMap
- Комбинируй с filter для оптимизации
- Используй для работы с N-to-M отношениями в данных
flatMap — это мощный инструмент для работы с вложенными структурами. Правильное использование делает код компактным и читаемым.