← Назад к вопросам
Что на входе в flatMap()
1.7 Middle🔥 91 комментариев
#Stream API и функциональное программирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
flatMap() в Java Streams
flatMap() — это промежуточная операция в Stream API, которая принимает на входе функцию, возвращающую Stream, и объединяет ("сплющивает") все эти потоки в один.
Сигнатура метода
// Для обычных потоков
public final <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
// Для потоков примитивов
public final IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper)
public final LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper)
public final DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper)
Что входит в flatMap()?
На входе в flatMap() находится:
- Функция (Function), которая получает элемент T из потока
- Эта функция должна возвращать Stream<R> — поток элементов
- Результат — новый Stream, содержащий все элементы из всех вложенных потоков
Простой пример
import java.util.List;
import java.util.Arrays;
public class FlatMapExample {
public static void main(String[] args) {
List<String> words = Arrays.asList(
"Hello", "World", "Java"
);
// На входе в flatMap() — функция String -> Stream<String>
words.stream()
.flatMap(word -> Arrays.stream(word.split(""))) // разбиваем на буквы
.forEach(System.out::println);
// Вывод:
// H e l l o W o r l d J a v a
}
}
Как это работает:
- Берём "Hello"
.split("")даёт массив ["H", "e", "l", "l", "o"]Arrays.stream()преобразует это вStream<String>- flatMap() собирает все эти потоки в один
forEach()печатает каждый элемент
Практический пример: список списков
import java.util.List;
import java.util.ArrayList;
public class NestedListExample {
public static void main(String[] args) {
// Вложенная структура
List<List<Integer>> nestedLists = new ArrayList<>();
nestedLists.add(Arrays.asList(1, 2, 3));
nestedLists.add(Arrays.asList(4, 5, 6));
nestedLists.add(Arrays.asList(7, 8, 9));
// Без flatMap() — получим Stream<List<Integer>>
nestedLists.stream()
.map(list -> list.size()) // Stream<Integer> с размерами
.forEach(System.out::println); // 3 3 3
System.out.println("---");
// С flatMap() — получим Stream<Integer>
nestedLists.stream()
.flatMap(list -> list.stream()) // На входе: List -> Stream<Integer>
.forEach(System.out::println); // 1 2 3 4 5 6 7 8 9
}
}
Сравнение map() vs flatMap()
public class MapVsFlatMap {
public static void main(String[] args) {
List<String> words = Arrays.asList("Hello", "World");
// map() — преобразует элемент в другой элемент
// Результат: Stream<Stream<String>>
words.stream()
.map(word -> Arrays.stream(word.split("")))
.forEach(stream -> System.out.println(stream)); // печатает Stream@...
System.out.println("---");
// flatMap() — преобразует в Stream и объединяет
// Результат: Stream<String>
words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.forEach(System.out::println); // печатает H e l l o W o r l d
}
}
На входе в flatMap() — это функция!
// Все эти варианты — функции, входящие в flatMap()
public class FunctionExamples {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
// Вариант 1: Лямбда с явным типом
numbers.stream()
.flatMap((Integer num) -> Stream.of(num, num * 2, num * 3))
.forEach(System.out::println);
System.out.println("---");
// Вариант 2: Лямбда с явным типом (совпадает со строкой выше)
numbers.stream()
.flatMap(num -> Stream.of(num, num * 2, num * 3))
.forEach(System.out::println);
System.out.println("---");
// Вариант 3: Method Reference
numbers.stream()
.flatMap(num -> getStream(num))
.forEach(System.out::println);
System.out.println("---");
// Вариант 4: Method Reference (если метод статический)
numbers.stream()
.flatMap(FunctionExamples::getStream)
.forEach(System.out::println);
}
// Метод, возвращающий Stream
static Stream<Integer> getStream(Integer num) {
return Stream.of(num, num * 2, num * 3);
}
}
Реальный пример: поиск в БД
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
// Получить все заказы всех пользователей
public List<Order> getAllOrders() {
return userRepository.findAll()
.stream()
// flatMap() принимает User -> Stream<Order>
.flatMap(user -> orderRepository.findByUserId(user.getId()).stream())
.collect(Collectors.toList());
}
// Получить все товары из всех заказов всех пользователей
public List<Product> getAllProducts() {
return userRepository.findAll()
.stream()
.flatMap(user -> orderRepository.findByUserId(user.getId()).stream())
.flatMap(order -> order.getProducts().stream())
.distinct()
.collect(Collectors.toList());
}
}
Потоки примитивов (IntStream, LongStream, DoubleStream)
public class PrimitiveStreams {
public static void main(String[] args) {
List<Integer> lists = Arrays.asList(1, 2, 3);
// flatMapToInt() — на входе функция Int -> IntStream
int sum = lists.stream()
.flatMapToInt(num -> IntStream.range(1, num + 1))
.sum(); // 1 + (1+2) + (1+2+3) = 9
System.out.println("Sum: " + sum);
// flatMapToDouble() — на входе функция T -> DoubleStream
double average = lists.stream()
.flatMapToDouble(num -> DoubleStream.of(num, num * 1.5))
.average()
.orElse(0);
System.out.println("Average: " + average);
}
}
Сложный практический пример
@Service
public class SearchService {
// Поиск всех комментариев ко всем вопросам автора
public List<Comment> getAuthorCommentsOnQuestions(String authorId) {
return questionRepository.findByAuthorId(authorId)
.stream()
// На входе: Question -> Stream<Comment>
.flatMap(question -> question.getComments().stream())
.filter(comment -> comment.getLikes() > 10)
.sorted(Comparator.comparing(Comment::getCreatedAt).reversed())
.limit(20)
.collect(Collectors.toList());
}
// Преобразование многоуровневой иерархии
public List<String> getAllCategoryTags() {
return categoryRepository.findAll()
.stream()
// На входе: Category -> Stream<SubCategory>
.flatMap(cat -> cat.getSubcategories().stream())
// На входе: SubCategory -> Stream<Tag>
.flatMap(subcat -> subcat.getTags().stream())
// На входе: Tag -> Stream<String>
.flatMap(tag -> Arrays.stream(tag.getName().split(",")))
.map(String::trim)
.distinct()
.sorted()
.collect(Collectors.toList());
}
}
Производительность flatMap()
// ⚠️ Внимание: flatMap() требует ресурсов!
// Каждый вызов функции создаёт новый Stream
// Хорошо для малого объёма данных
List<Order> orders = userRepository.findAll() // 100 пользователей
.stream()
.flatMap(user -> orderRepository.findByUserId(user.getId()).stream())
.collect(Collectors.toList());
// Плохо для большого объёма
List<Order> orders = userRepository.findAll() // 1 млн пользователей
.stream()
.flatMap(user -> orderRepository.findByUserId(user.getId()).stream())
.collect(Collectors.toList()); // Создаст 1 млн запросов к БД!
// Лучше использовать join в SQL
List<Order> orders = orderRepository.findAllWithUsers();
Ключевые выводы
- На входе в flatMap() — функция, которая преобразует T в Stream<R>
- На выходе — новый Stream<R>, содержащий все элементы из всех вложенных потоков
- map() vs flatMap(): map создаёт Stream<Stream<T>>, flatMap создаёт Stream<T>
- Используй flatMap() для развёртывания вложенных структур
- Будь осторожен с производительностью при больших объёмах данных
flatMap() — мощный инструмент для работы со сложными иерархиями данных!