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

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

Ответ

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

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

  • Цель: Преобразовать или отфильтровать поток элементов, создавая новый поток для дальнейшей обработки.
  • Поведение: Ленивые. Они не выполняют немедленных вычислений, а лишь добавляют операцию в конвейер (pipeline).
  • Возвращаемое значение: Stream<T>.
  • Примеры: filter(Predicate<T>), map(Function<T, R>), sorted(), distinct(), limit(long n), peek(Consumer<T>).

Терминальные операции:

  • Цель: Завершить обработку потока, получив конечный результат (значение, коллекцию или побочный эффект).
  • Поведение: Энергичные (eager). Вызов терминальной операции запускает выполнение всего накопленного конвейера промежуточных операций.
  • Возвращаемое значение: Не Stream (это может быть void, Optional, List, boolean, long и т.д.).
  • Примеры: collect(Collector), forEach(Consumer<T>), reduce(...), count(), anyMatch(Predicate<T>), findFirst().

Ключевое следствие: Поскольку промежуточные операции ленивы, поток данных будет обработан за один проход (при вызове терминальной операции), что может быть эффективнее нескольких итераций по коллекции.

Пример, демонстрирующий ленивость:

List<String> list = Arrays.asList("a1", "b2", "c3", "a2");

list.stream()
    .filter(s -> {
        System.out.println("filter: " + s); // Вывод появится только после вызова .findFirst()
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("map: " + s); // Аналогично
        return s.toUpperCase();
    })
    .findFirst(); // Терминальная операция, которая запускает обработку
// Вывод в консоль:
// filter: a1
// map: a1
// Обработка остановилась после нахождения первого подходящего элемента.