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

Что такое терминальная операция?

2.0 Middle🔥 241 комментариев
#Stream API и функциональное программирование

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

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

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

# Терминальные операции в Java Stream API

Терминальная операция — это завершающая операция в цепи потока (Stream), которая запускает обработку элементов и возвращает результат.

Основное различие

// Промежуточные операции (Intermediate)
Stream<Integer> stream = numbers.stream()
    .filter(n -> n > 5)           // filter() — промежуточная
    .map(n -> n * 2)              // map() — промежуточная
    // Вычисления ещё НЕ произошли!
    // Это просто цепь инструкций

// Терминальная операция запускает вычисления
List<Integer> result = stream.collect(Collectors.toList()); // collect() — терминальная
// Теперь произошло реальное выполнение!

Ключевые свойства терминальных операций

  1. Запускают вычисления — без терминальной операции ничего не происходит
  2. Возвращают конкретный результат — не Stream
  3. После них нельзя добавлять операции — поток закрыт
  4. Потребляют элементы — элементы обрабатываются по одному

Основные терминальные операции

1. forEach() — обход элементов

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// forEach() — терминальная, ничего не возвращает (void)
names.stream()
    .filter(n -> n.length() > 3)
    .forEach(System.out::println); // Печатает: Alice, Charlie

// Эквивалентно:
for (String name : names) {
    if (name.length() > 3) {
        System.out.println(name);
    }
}

2. collect() — сбор результатов

// Сбор в List
List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

// Сбор в Set
Set<Integer> uniqueNumbers = numbers.stream()
    .collect(Collectors.toSet());

// Сбор в Map
Map<Integer, String> idToName = people.stream()
    .collect(Collectors.toMap(
        Person::getId,      // ключ
        Person::getName     // значение
    ));

// Сбор с дополнительной логикой
Map<String, List<Person>> byCity = people.stream()
    .collect(Collectors.groupingBy(
        Person::getCity,    // группировка по городу
        Collectors.toList() // результат — список людей в каждом городе
    ));

// Пример результата:
// {Moscow: [Alice, Bob], SPb: [Charlie]}

3. reduce() — свёртка данных

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Сумма всех чисел
int sum = numbers.stream()
    .reduce(0, (acc, num) -> acc + num); // 15

// Пример пошагово:
// acc=0, num=1 → 0+1 = 1
// acc=1, num=2 → 1+2 = 3
// acc=3, num=3 → 3+3 = 6
// acc=6, num=4 → 6+4 = 10
// acc=10, num=5 → 10+5 = 15

// Произведение чисел
int product = numbers.stream()
    .reduce(1, (acc, num) -> acc * num); // 120

// Optional версия (если поток пуст)
Optional<Integer> max = numbers.stream()
    .reduce((a, b) -> a > b ? a : b);
if (max.isPresent()) {
    System.out.println("Max: " + max.get());
}

4. count() — количество элементов

long evenCount = numbers.stream()
    .filter(n -> n % 2 == 0)
    .count(); // 5

// Эквивалентно:
long count = 0;
for (int num : numbers) {
    if (num % 2 == 0) count++;
}

5. findFirst(), findAny() — поиск элемента

Optional<Integer> first = numbers.stream()
    .filter(n -> n > 5)
    .findFirst(); // Optional содержит первый элемент > 5

if (first.isPresent()) {
    System.out.println(first.get());
}

// Альтернатива
int firstValue = numbers.stream()
    .filter(n -> n > 5)
    .findFirst()
    .orElse(-1); // -1 если не найден

// findAny() — может вернуть любой элемент
Optional<Integer> any = numbers.stream()
    .filter(n -> n > 5)
    .findAny();

6. anyMatch(), allMatch(), noneMatch() — проверка условия

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Есть ли хотя бы один элемент > 3?
boolean hasLarge = numbers.stream()
    .anyMatch(n -> n > 3); // true

// Все ли элементы > 0?
boolean allPositive = numbers.stream()
    .allMatch(n -> n > 0); // true

// Нет ли элементов < 0?
boolean noNegative = numbers.stream()
    .noneMatch(n -> n < 0); // true

// Реальный пример
List<User> users = userRepository.findAll();
boolean allActive = users.stream()
    .allMatch(User::isActive); // Все ли пользователи активны?

7. min(), max() — минимум и максимум

Optional<Integer> min = numbers.stream()
    .min(Integer::compare); // Optional.of(1)

Optional<Integer> max = numbers.stream()
    .max(Integer::compare); // Optional.of(5)

// С объектами
Optional<Person> youngest = people.stream()
    .min(Comparator.comparingInt(Person::getAge));

if (youngest.isPresent()) {
    System.out.println("Youngest: " + youngest.get().getName());
}

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

class User {
    private Long id;
    private String name;
    private String city;
    private int age;
    // getters...
}

// Сложный запрос с несколькими промежуточными операциями
// и одной терминальной
var result = users.stream()
    // Промежуточные операции
    .filter(u -> u.getAge() > 18)                // Фильтр
    .filter(u -> "Moscow".equals(u.getCity()))   // Ещё фильтр
    .map(u -> u.getName().toUpperCase())         // Преобразование
    .distinct()                                   // Удаление дубликатов
    .sorted()                                     // Сортировка
    
    // Терминальная операция — запускает все вычисления
    .collect(Collectors.toList());                // Сбор в List

System.out.println(result);
// Вывод: [ALICE, BOB, CHARLIE]

Важная особенность: Ленивое вычисление

// Это НЕ выполняется до терминальной операции!
Stream<Integer> stream = numbers.stream()
    .filter(n -> {
        System.out.println("Checking: " + n); // Не печатается!
        return n > 3;
    })
    .map(n -> {
        System.out.println("Mapping: " + n); // Не печатается!
        return n * 2;
    });

// Теперь выполняется (с терминальной операцией)
List<Integer> result = stream.collect(Collectors.toList());
// Печатает:
// Checking: 1, 2, 3, 4, 5
// Mapping: 4, 5

Сравнение терминальных и промежуточных

ОперацияТипВозвращаетПример
filterПромежуточнаяStream.filter(x > 5)
mapПромежуточнаяStream.map(x -> x * 2)
flatMapПромежуточнаяStream.flatMap(x -> x.getChildren())
distinctПромежуточнаяStream.distinct()
sortedПромежуточнаяStream.sorted()
forEachТерминальнаяvoid.forEach(System.out::println)
collectТерминальнаяCollection.collect(toList())
reduceТерминальнаяOptional/Value.reduce(0, Integer::sum)
countТерминальнаяlong.count()
findFirstТерминальнаяOptional.findFirst()
anyMatchТерминальнаяboolean.anyMatch(x > 5)

Вывод

Терминальная операция — это завершение конвейера Stream'а, которое:

  1. Запускает обработку элементов
  2. Возвращает конкретный результат
  3. Закрывает поток (больше нельзя добавлять операции)

Без терминальной операции — это просто описание плана, а не выполнение.

Что такое терминальная операция? | PrepBro