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

Когда начинается выполнение Stream?

1.0 Junior🔥 111 комментариев
#Soft Skills и карьера

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

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

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

Когда начинается выполнение Stream

Ключевая концепция: ленивое выполнение

Потоки Java (Stream) используют принцип ленивого выполнения (lazy evaluation). Это означает, что промежуточные операции не выполняются немедленно — они выполняются только при вызове терминальной операции.

Типы операций в Stream

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

Эти операции создают новый Stream и не начинают обработку данных:

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

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

// На этом этапе ничего не напечатается!
System.out.println("Stream создан, но не выполнялся");

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

  • filter() — фильтрация элементов
  • map() — преобразование элементов
  • flatMap() — плоское отображение
  • sorted() — сортировка
  • distinct() — удаление дубликатов
  • limit() — ограничение количества
  • skip() — пропуск элементов

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

Терминальные операции запускают выполнение всей цепочки Stream:

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

// Терминальная операция ЗАПУСКАЕТ выполнение
List<Integer> result = numbers.stream()
    .filter(n -> {
        System.out.println("filter: " + n);
        return n > 2;
    })
    .map(n -> {
        System.out.println("map: " + n);
        return n * 2;
    })
    .collect(Collectors.toList());  // ЗДЕСЬ НАЧАЛОСЬ ВЫПОЛНЕНИЕ!

System.out.println("Результат: " + result);

Вывод:

filter: 1
filter: 2
filter: 3
map: 3
filter: 4
map: 4
filter: 5
map: 5
Результат: [6, 8, 10]

Примеры терминальных операций:

  • collect() — сбор результатов
  • forEach() — обход элементов
  • reduce() — свертка потока
  • count() — подсчет элементов
  • findFirst() — поиск первого
  • findAny() — поиск любого
  • anyMatch(), allMatch(), noneMatch() — проверка условий
  • toArray() — преобразование в массив
  • min(), max() — поиск минимума/максимума

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

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

Порядок выполнения: вертикальный vs горизонтальный

Вертикальное выполнение (по элементам)

Stream обрабатывает каждый элемент через все операции, прежде чем перейти к следующему:

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

numbers.stream()
    .filter(n -> {
        System.out.println("filter: " + n);
        return n > 1;
    })
    .map(n -> {
        System.out.println("map: " + n);
        return n * 2;
    })
    .limit(2)  // Ограничение!
    .forEach(System.out::println);

// Вывод:
// filter: 1
// filter: 2
// map: 2      <- начало обработки
// 4
// filter: 3
// map: 3
// 6          <- конец, limit(2) остановил выполнение
// Остальные элементы (4, 5) не обработаны!

Это важно для оптимизации производительности.

Состояние Stream

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

Stream<Integer> stream = numbers.stream()
    .filter(n -> n > 2);

// Статус: промежуточная операция, ничего не выполнялось

List<Integer> result = stream.collect(Collectors.toList());
// Статус: ВЫПОЛНЕНО

// ❌ ОШИБКА: Stream уже был выполнен!
try {
    stream.forEach(System.out::println);
} catch (IllegalStateException e) {
    System.out.println("IllegalStateException: stream has already been consumed");
}

Параллельные потоки

Для параллельных потоков (parallel streams) выполнение также ленивое:

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

// Параллельное выполнение начинается при терминальной операции
List<Integer> result = numbers.parallelStream()
    .filter(n -> {
        System.out.println(Thread.currentThread().getName() + ": filter " + n);
        return n > 2;
    })
    .map(n -> {
        System.out.println(Thread.currentThread().getName() + ": map " + n);
        return n * 2;
    })
    .collect(Collectors.toList());  // ВЫПОЛНЕНИЕ ЗАПУЩЕНО

System.out.println("Результат: " + result);

Таблица операций

ОперацияТипПримеры
filterПромежуточнаяstream.filter(x -> x > 5)
mapПромежуточнаяstream.map(x -> x * 2)
flatMapПромежуточнаяstream.flatMap(x -> x.stream())
sortedПромежуточнаяstream.sorted()
distinctПромежуточнаяstream.distinct()
limitПромежуточнаяstream.limit(10)
skipПромежуточнаяstream.skip(5)
forEachТерминальнаяstream.forEach(System.out::println)
collectТерминальнаяstream.collect(Collectors.toList())
reduceТерминальнаяstream.reduce((a, b) -> a + b)
countТерминальнаяstream.count()
anyMatchТерминальнаяstream.anyMatch(x -> x > 5)
findFirstТерминальнаяstream.findFirst()

Ошибки при работе с Stream

// ❌ Ошибка 1: нет терминальной операции
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream()
    .filter(n -> n > 2);
// Вычисления не произойдут!

// ✅ Исправление
List<Integer> result = numbers.stream()
    .filter(n -> n > 2)
    .collect(Collectors.toList());

// ❌ Ошибка 2: повторное использование
Stream<Integer> stream2 = numbers.stream();
stream2.forEach(System.out::println);
stream2.forEach(System.out::println);  // IllegalStateException!

// ✅ Исправление
List<Integer> numbers2 = Arrays.asList(1, 2, 3, 4, 5);
numbers2.stream().forEach(System.out::println);
numbers2.stream().forEach(System.out::println);  // Новый Stream каждый раз

Заключение

Stream начинает выполняться ТОЛЬКО при вызове терминальной операции. Все промежуточные операции (filter, map, sorted и т.д.) просто создают цепочку преобразований, которая фактически применяется только когда вызывается терминальная операция (collect, forEach, count и т.д.). Это ленивое выполнение позволяет:

  • Оптимизировать производительность через short-circuit операции (limit, anyMatch)
  • Минимизировать использование памяти
  • Обрабатывать бесконечные потоки данных

Помни: без терминальной операции — выполнения не будет!

Когда начинается выполнение Stream? | PrepBro