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

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

1.6 Junior🔥 221 комментариев
#Stream API и функциональное программирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Промежуточные и терминальные операторы в Stream API

В Java Stream API операции делятся на две категории: промежуточные (intermediate) и терминальные (terminal). Это разделение критично для понимания lazy evaluation и эффективной работы с потоками данных.

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

Промежуточные операторы возвращают новый Stream и позволяют цепочку вызовов. Они НЕ выполняют вычисления — только описывают трансформацию.

// Промежуточные операторы
Stream<String> stream = Arrays.stream(new String[]{"apple", "banana", "cherry"});

Stream<String> filtered = stream.filter(s -> s.length() > 5);      // ПРОМЕЖУТОЧНЫЙ
Stream<String> mapped = stream.map(String::toUpperCase);           // ПРОМЕЖУТОЧНЫЙ
Stream<Integer> flatMapped = stream.flatMap(s -> 
    Stream.of(s.split(""))).map(String::length);                    // ПРОМЕЖУТОЧНЫЙ
Stream<String> distinct = stream.distinct();                        // ПРОМЕЖУТОЧНЫЙ
Stream<String> sorted = stream.sorted();                            // ПРОМЕЖУТОЧНЫЙ
Stream<String> limited = stream.limit(10);                          // ПРОМЕЖУТОЧНЫЙ
Stream<String> skipped = stream.skip(5);                            // ПРОМЕЖУТОЧНЫЙ
Stream<String> peeked = stream.peek(System.out::println);           // ПРОМЕЖУТОЧНЫЙ

Ключевая особенность: ничего не происходит до терминальной операции!

// Этот код НЕ выполняет никаких вычислений
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Stream<Integer> processed = numbers.stream()
    .filter(n -> {
        System.out.println("Фильтр: " + n); // Это НЕ выполнится!
        return n > 2;
    })
    .map(n -> {
        System.out.println("Маппинг: " + n); // Это НЕ выполнится!
        return n * 2;
    });

// Stream создан, но ничего не произошло!
System.out.println("Stream создан, но не выполнен");

Терминальные операторы (Terminal)

Терминальные операторы НЕ возвращают Stream. Они запускают вычисления (lazy evaluation) и возвращают конечный результат.

// Терминальные операторы

// forEach - выполнить действие для каждого элемента
stream.forEach(System.out::println);                    // ТЕРМИНАЛЬНЫЙ

// collect - собрать результаты в коллекцию
List<String> list = stream.collect(Collectors.toList()); // ТЕРМИНАЛЬНЫЙ

// reduce - агрегировать значения
int sum = stream.reduce(0, Integer::sum);               // ТЕРМИНАЛЬНЫЙ

// count - подсчитать элементы
long count = stream.count();                             // ТЕРМИНАЛЬНЫЙ

// findFirst, findAny - найти элемент
Optional<String> first = stream.findFirst();             // ТЕРМИНАЛЬНЫЙ
Optional<String> any = stream.findAny();                 // ТЕРМИНАЛЬНЫЙ

// match операторы - проверить условие
boolean anyMatch = stream.anyMatch(s -> s.length() > 5); // ТЕРМИНАЛЬНЫЙ
boolean allMatch = stream.allMatch(s -> s.length() > 0); // ТЕРМИНАЛЬНЫЙ
boolean noneMatch = stream.noneMatch(s -> s.length() > 10); // ТЕРМИНАЛЬНЫЙ

// min, max - найти экстремум
Optional<String> min = stream.min(String::compareTo);   // ТЕРМИНАЛЬНЫЙ
Optional<String> max = stream.max(String::compareTo);   // ТЕРМИНАЛЬНЫЙ

Lazy Evaluation

public class LazyEvaluationDemo {
    public static void main(String[] args) {
        System.out.println("=== БЕЗ терминальной операции ===");
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        Stream<Integer> processed = numbers.stream()
            .filter(n -> {
                System.out.println("Фильтр: " + n);
                return n > 2;
            })
            .map(n -> {
                System.out.println("Маппинг: " + n);
                return n * 2;
            });
        
        System.out.println("Stream создан, ничего не напечатано!");
        
        System.out.println("\n=== С терминальной операцией ===");
        // Теперь выполнится all операции
        List<Integer> result = numbers.stream()
            .filter(n -> {
                System.out.println("Фильтр: " + n);
                return n > 2;
            })
            .map(n -> {
                System.out.println("Маппинг: " + n);
                return n * 2;
            })
            .collect(Collectors.toList()); // ТЕРМИНАЛЬНАЯ операция!
        
        System.out.println("Результат: " + result);
        
        // Вывод:
        // Фильтр: 1
        // Фильтр: 2
        // Фильтр: 3
        // Маппинг: 3
        // Фильтр: 4
        // Маппинг: 4
        // Фильтр: 5
        // Маппинг: 5
        // Результат: [6, 8, 10]
    }
}

Порядок выполнения

public class ExecutionOrder {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList(
            "apple", "banana", "apricot", "cherry"
        );
        
        // Порядок выполнения: для КАЖДОГО элемента выполняются ВСЕ промежуточные операции
        List<String> result = fruits.stream()
            .filter(f -> {
                System.out.println("Filter: " + f);
                return f.startsWith("a");
            })
            .map(f -> {
                System.out.println("Map: " + f);
                return f.toUpperCase();
            })
            .limit(2)
            .collect(Collectors.toList());
        
        // Вывод показывает, что limit() останавливает обработку
        // Filter: apple
        // Map: apple
        // Filter: banana
        // Filter: apricot
        // Map: apricot
        // (cherry не обработан, потому что limit(2) уже достигнут)
        
        System.out.println("Результат: " + result);
    }
}

Практическое применение

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

// Фильтрация и преобразование
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> evens = numbers.stream()
    .filter(n -> n % 2 == 0)        // ПРОМЕЖУТОЧНЫЙ
    .map(n -> n * n)                // ПРОМЕЖУТОЧНЫЙ
    .sorted()                        // ПРОМЕЖУТОЧНЫЙ
    .collect(Collectors.toList());  // ТЕРМИНАЛЬНЫЙ

System.out.println(evens); // [4, 16, 36]

Оптимизация с limit()

// limit() быстро останавливает обработку
List<String> words = Arrays.asList(
    "apple", "apricot", "banana", "blueberry", "cherry"
);

List<String> result = words.stream()
    .filter(w -> {
        System.out.println("Проверка: " + w);
        return w.startsWith("a");
    })
    .limit(2)                       // Остановит после 2 элементов
    .collect(Collectors.toList());

// Вывод:
// Проверка: apple
// Проверка: apricot
// (остальные не проверяются благодаря limit)

peek() для отладки

// peek() — это промежуточный оператор для отладки
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> result = numbers.stream()
    .filter(n -> n > 2)
    .peek(n -> System.out.println("После фильтра: " + n))  // ПРОМЕЖУТОЧНЫЙ
    .map(n -> n * 2)
    .peek(n -> System.out.println("После маппинга: " + n)) // ПРОМЕЖУТОЧНЫЙ
    .collect(Collectors.toList());                         // ТЕРМИНАЛЬНЫЙ

// Вывод:
// После фильтра: 3
// После маппинга: 6
// После фильтра: 4
// После маппинга: 8
// и т.д.

Таблица операторов

ОперацияТипВозвращаетПример
filterпромежуточныйStream.filter(x -> x > 5)
mapпромежуточныйStream.map(String::toUpperCase)
flatMapпромежуточныйStream.flatMap(x -> Stream.of(...))
distinctпромежуточныйStream.distinct()
sortedпромежуточныйStream.sorted()
limitпромежуточныйStream.limit(10)
skipпромежуточныйStream.skip(5)
peekпромежуточныйStream.peek(System.out::println)
forEachтерминальныйvoid.forEach(...)
collectтерминальныйCollection.collect(Collectors.toList())
reduceтерминальныйOptional/Value.reduce(0, Integer::sum)
countтерминальныйlong.count()
findFirstтерминальныйOptional.findFirst()
anyMatchтерминальныйboolean.anyMatch(x -> x > 5)
allMatchтерминальныйboolean.allMatch(x -> x > 0)
noneMatchтерминальныйboolean.noneMatch(x -> x < 0)
min/maxтерминальныйOptional.max(...)

Важные правила

  1. Stream можно использовать только один раз:
Stream<Integer> stream = numbers.stream();
stream.forEach(System.out::println);  // OK
stream.forEach(System.out::println);  // IllegalStateException!
  1. Промежуточные операции ленивые:
// Это НЕ выполнится без терминальной операции
numbers.stream()
    .filter(n -> n > 2)
    .map(n -> n * 2);
  1. Выбирайте правильный терминальный оператор:
// Если нужна коллекция
List<Integer> list = stream.collect(Collectors.toList());

// Если нужно значение
int sum = stream.reduce(0, Integer::sum);

// Если нужна сторона эффект
stream.forEach(System.out::println);

Понимание разницы между промежуточными и терминальными операторами — это ключ к эффективному использованию Stream API и написанию производительного кода.

В чем разница между промежуточными и терминальными операторами? | PrepBro