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

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

Ответ

Операции в Stream API делятся на два типа, которые кардинально отличаются по своему поведению и назначению в конвейере обработки данных.

Промежуточные операции (Intermediate Operations):

  • Ленивые (Lazy): Не выполняются немедленно. Они лишь описывают преобразование и добавляются в конвейер.
  • Возвращают Stream: Результатом всегда является новый объект Stream, что позволяет строить цепочки вызовов.
  • Примеры: filter(), map(), flatMap(), distinct(), sorted(), peek(), limit(), skip().

Терминальные операции (Terminal Operations):

  • Энергичные (Eager): Запускают выполнение всего конвейера. Без терминальной операции поток не обработается.
  • Завершают поток: Поток после вызова терминальной операции считается потребленным и не может быть использован повторно.
  • Возвращают не-Stream: Результатом может быть значение (reduce()), коллекция (collect()), примитив (count()) или void (forEach()).
  • Примеры: forEach(), collect(), reduce(), count(), anyMatch(), allMatch(), findFirst(), min(), max().

Пример потока с разбором операций:

List<String> result = names.stream()          // Источник потока
        .filter(name -> name.length() > 3)    // Промежуточная: фильтрация
        .map(String::toUpperCase)             // Промежуточная: преобразование
        .sorted()                             // Промежуточная: сортировка
        .collect(Collectors.toList());        // Терминальная: сбор в список
// 1. Поток создан (stream())
// 2-4. Конвейер из промежуточных операций построен, но не выполнен.
// 5. Вызов collect() запускает весь конвейер: фильтрацию, маппинг, сортировку и сбор результата.

Важное правило: Поток можно обработать только одной терминальной операцией.