В чем разница между промежуточными и терминальными операциями в Stream API Java?

«В чем разница между промежуточными и терминальными операциями в Stream API Java?» — вопрос из категории Java, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Java Stream API операции делятся на промежуточные (intermediate) и терминальные (terminal), что определяет их поведение в цепочке вызовов.

Промежуточные операции (например, filter(), map(), sorted(), distinct()):

  • Ленивые (lazy) — не выполняют обработку немедленно, а лишь добавляют новую операцию в конвейер.
  • Возвращают новый Stream, что позволяет строить цепочки (pipeline).
  • Обработка данных начинается только при вызове терминальной операции.

Терминальные операции (например, collect(), forEach(), reduce(), count(), anyMatch()):

  • Энергичные (eager) — запускают выполнение всего конвейера, обрабатывают элементы и производят результат или побочный эффект.
  • Возвращают не Stream, а конкретное значение (void, Optional, List, boolean и т.д.).
  • После вызова терминальной операции поток считается потребленным и не может быть использован повторно.

Пример:

List<String> result = names.stream()          // Источник потока
        .filter(name -> name.length() > 3)   // Промежуточная операция: фильтрация
        .map(String::toUpperCase)            // Промежуточная операция: преобразование
        .collect(Collectors.toList());       // Терминальная операция: сбор в список

В этом примере filter и map лишь настраивают конвейер. Фактический обход коллекции names, применение фильтра и маппера происходит внутри вызова collect().