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

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

2.0 Middle🔥 231 комментариев
#Stream API и функциональное программирование#Основы Java

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

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

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

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

Stream API (введён в Java 8) основан на двух типах операций: промежуточных и терминальных. Понимание различия между ними критично для эффективной работы со Stream'ами.

Промежуточные методы (Intermediate Operations)

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

public class IntermediateOperations {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        
        // filter() - промежуточный метод
        Stream<Integer> filtered = numbers.stream()
            .filter(n -> {
                System.out.println("filter: " + n);
                return n > 2;
            });
        
        // НЕ выполнилось! filter ленив
        System.out.println("Stream создан, но filter не вызывался");
        
        // map() - промежуточный метод
        Stream<Integer> mapped = filtered.map(n -> {
            System.out.println("map: " + n);
            return n * 2;
        });
        
        // Всё ещё ничего не выполнилось!
        System.out.println("map добавлена, но не вызывалась");
    }
}

Основные промежуточные методы:

public class AllIntermediateMethods {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // filter(predicate) - оставляет элементы, соответствующие условию
        Stream<Integer> s1 = numbers.stream()
            .filter(n -> n > 2);  // 3, 4, 5
        
        // map(function) - трансформирует элементы
        Stream<Integer> s2 = numbers.stream()
            .map(n -> n * 2);  // 2, 4, 6, 8, 10
        
        // flatMap(function) - трансформирует в Stream и объединяет
        Stream<Integer> s3 = numbers.stream()
            .flatMap(n -> Arrays.stream(new int[]{n, n*2}));
            // 1, 2, 2, 4, 3, 6, 4, 8, 5, 10
        
        // distinct() - убирает дубликаты
        List<Integer> withDuplicates = Arrays.asList(1, 2, 2, 3, 3, 3);
        Stream<Integer> s4 = withDuplicates.stream()
            .distinct();  // 1, 2, 3
        
        // sorted() - сортирует элементы
        Stream<Integer> s5 = numbers.stream()
            .sorted();  // 1, 2, 3, 4, 5
        
        // limit(n) - берёт первые n элементов
        Stream<Integer> s6 = numbers.stream()
            .limit(3);  // 1, 2, 3
        
        // skip(n) - пропускает первые n элементов
        Stream<Integer> s7 = numbers.stream()
            .skip(2);  // 3, 4, 5
        
        // peek(consumer) - промежуточный способ отладки
        Stream<Integer> s8 = numbers.stream()
            .peek(n -> System.out.println("Элемент: " + n));
    }
}

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

Терминальные методы завершают Stream и возвращают результат. После терминального метода Stream'ы больше нельзя использовать. Терминальный метод ВЫНУЖДАЕТ выполнение всех промежуточных операций.

public class TerminalOperations {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        
        // forEach(consumer) - выполняет действие для каждого элемента
        numbers.stream()
            .filter(n -> n > 2)
            .forEach(n -> System.out.println(n));  // ТЕРМИНАЛЬНЫЙ
        // Вывод: 3 4 5
        
        // collect(collector) - собирает результаты в коллекцию
        List<Integer> result = numbers.stream()
            .filter(n -> n > 2)
            .collect(Collectors.toList());  // ТЕРМИНАЛЬНЫЙ
        System.out.println(result);  // [3, 4, 5]
        
        // reduce(identity, accumulator) - свёртка
        int sum = numbers.stream()
            .reduce(0, (acc, n) -> acc + n);  // ТЕРМИНАЛЬНЫЙ
        System.out.println(sum);  // 21
        
        // count() - подсчёт элементов
        long count = numbers.stream()
            .filter(n -> n > 2)
            .count();  // ТЕРМИНАЛЬНЫЙ
        System.out.println(count);  // 4
        
        // findFirst() - первый элемент
        Optional<Integer> first = numbers.stream()
            .filter(n -> n > 2)
            .findFirst();  // ТЕРМИНАЛЬНЫЙ
        
        // findAny() - любой элемент
        Optional<Integer> any = numbers.stream()
            .filter(n -> n > 2)
            .findAny();  // ТЕРМИНАЛЬНЫЙ
        
        // anyMatch(predicate) - есть ли элемент, соответствующий условию
        boolean hasEven = numbers.stream()
            .anyMatch(n -> n % 2 == 0);  // ТЕРМИНАЛЬНЫЙ
        System.out.println(hasEven);  // true
        
        // allMatch(predicate) - все элементы соответствуют условию
        boolean allPositive = numbers.stream()
            .allMatch(n -> n > 0);  // ТЕРМИНАЛЬНЫЙ
        System.out.println(allPositive);  // true
        
        // min/max - минимум и максимум
        Optional<Integer> min = numbers.stream().min(Integer::compareTo);
        Optional<Integer> max = numbers.stream().max(Integer::compareTo);
    }
}

Ленивые вычисления (Lazy Evaluation)

Это ключевое отличие — промежуточные методы выполняются ТОЛЬКО когда нужен результат.

public class LazyEvaluation {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        
        System.out.println("=== БЕЗ терминального метода ===");
        numbers.stream()
            .peek(n -> System.out.println("peek: " + n))
            .filter(n -> n > 2)
            .peek(n -> System.out.println("после filter: " + n))
            .map(n -> {
                System.out.println("map: " + n);
                return n * 2;
            });
        System.out.println("Ничего не напечатано!");
        
        System.out.println("\n=== С терминальным методом ===");
        numbers.stream()
            .peek(n -> System.out.println("peek: " + n))
            .filter(n -> n > 2)
            .peek(n -> System.out.println("после filter: " + n))
            .map(n -> {
                System.out.println("map: " + n);
                return n * 2;
            })
            .forEach(n -> System.out.println("результат: " + n));
        // Теперь всё выполняется!
    }
}

Вывод:

=== БЕЗ терминального метода ===
Ничего не напечатано!

=== С терминальным методом ===
peek: 1
peek: 2
peek: 3
после filter: 3
map: 3
результат: 6
peek: 4
после filter: 4
map: 4
результат: 8
peek: 5
после filter: 5
map: 5
результат: 10
peek: 6
после filter: 6
map: 6
результат: 12

Практический пример: обработка данных

public class StreamPipelineExample {
    static class User {
        String name;
        int age;
        String country;
        
        User(String name, int age, String country) {
            this.name = name;
            this.age = age;
            this.country = country;
        }
    }
    
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", 25, "USA"),
            new User("Bob", 35, "UK"),
            new User("Charlie", 28, "USA"),
            new User("Diana", 32, "Canada")
        );
        
        // Цепочка: промежуточные -> терминальный
        List<String> result = users.stream()
            .filter(u -> u.age > 25)              // промежуточный
            .filter(u -> u.country.equals("USA")) // промежуточный
            .map(u -> u.name)                     // промежуточный
            .sorted()                             // промежуточный
            .collect(Collectors.toList());        // ТЕРМИНАЛЬНЫЙ
        
        System.out.println(result);  // [Alice, Charlie]
    }
}

Сравнительная таблица

ПараметрПромежуточныйТерминальный
ВозвращаетStreamРезультат (не Stream)
ЛенивыйДаНет (выполняется сразу)
Можно цепитьДаНет
Примерыfilter, map, sortedforEach, collect, count
После негоМожет быть ещё операцияStream закончен

Ошибка: использование Stream после терминального метода

public class StreamError {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3);
        
        Stream<Integer> stream = numbers.stream()
            .filter(n -> n > 1);
        
        stream.forEach(System.out::println);  // терминальный
        
        // stream.forEach(System.out::println);  // ОШИБКА!
        // java.lang.IllegalStateException: stream has already been operated upon
    }
}

Промежуточные и терминальные методы — это основа Stream API. Понимание этого различия позволяет писать эффективный, ленивый и чистый код для обработки данных в Java.

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