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

Что на входе в 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
    }
}

Как это работает:

  1. Берём "Hello"
  2. .split("") даёт массив ["H", "e", "l", "l", "o"]
  3. Arrays.stream() преобразует это в Stream<String>
  4. flatMap() собирает все эти потоки в один
  5. 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() — мощный инструмент для работы со сложными иерархиями данных!