← Назад к вопросам
В какой момент выполняются промежуточные методы в Stream
2.0 Middle🔥 81 комментариев
#Stream API и функциональное программирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# В какой момент выполняются промежуточные методы в Stream?
Краткий ответ
Промежуточные методы выполняются ленивым образом — только когда вызывается терминальная операция. До этого они просто описывают преобразования.
Что такое промежуточные методы?
Промежуточные (intermediate) методы — это методы, которые возвращают новый Stream:
// Все эти методы - промежуточные
stream
.filter(x -> x > 5) // промежуточный
.map(x -> x * 2) // промежуточный
.distinct() // промежуточный
.limit(10) // промежуточный
.skip(2) // промежуточный
.flatMap(...) // промежуточный
.sorted() // промежуточный
Терминальные операции
Терминальные (terminal) методы — они не возвращают Stream и запускают вычисления:
.forEach(System.out::println) // терминальная
.collect(Collectors.toList()) // терминальная
.reduce((a, b) -> a + b) // терминальная
.findFirst() // терминальная
.count() // терминальная
.anyMatch(x -> x > 0) // терминальная
Ленивые вычисления на примере
Пример 1: Промежуточные методы без терминального
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Этот код НЕ выполнит ничего!
stream
.filter(x -> {
System.out.println("filter: " + x);
return x > 2;
})
.map(x -> {
System.out.println("map: " + x);
return x * 2;
});
// Консоль пуста!
// Промежуточные методы не вызваны
Пример 2: С терминальной операцией
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.filter(x -> {
System.out.println("filter: " + x);
return x > 2;
})
.map(x -> {
System.out.println("map: " + x);
return x * 2;
})
.collect(Collectors.toList()); // ТЕРМИНАЛЬНАЯ ОПЕРАЦИЯ!
// Вывод:
// filter: 1
// filter: 2
// filter: 3
// map: 3
// filter: 4
// map: 4
// filter: 5
// map: 5
System.out.println(result); // [6, 8, 10]
Порядок выполнения: Вертикальное или горизонтальное?
Неправильное понимание (горизонтальное)
Много людей ошибочно думают, что операции выполняются в таком порядке:
Шаг 1: filter все элементы → [3, 4, 5]
Шаг 2: map все элементы → [6, 8, 10]
Шаг 3: collect результат → [6, 8, 10]
Правильное понимание (вертикальное)
На самом деле каждый элемент проходит весь pipeline полностью:
Элемент 1:
1 → filter(1 > 2?) → НЕТ → пропущен
Элемент 2:
2 → filter(2 > 2?) → НЕТ → пропущен
Элемент 3:
3 → filter(3 > 2?) → ДА → map(3 * 2 = 6) → 6
Элемент 4:
4 → filter(4 > 2?) → ДА → map(4 * 2 = 8) → 8
Элемент 5:
5 → filter(5 > 2?) → ДА → map(5 * 2 = 10) → 10
Практические примеры
Пример с limit и skip
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.peek(x -> System.out.println("peek1: " + x))
.filter(x -> x > 2)
.peek(x -> System.out.println("peek2: " + x))
.skip(1)
.peek(x -> System.out.println("peek3: " + x))
.limit(2)
.peek(x -> System.out.println("peek4: " + x))
.forEach(x -> System.out.println("result: " + x));
Вывод:
peek1: 1
peek1: 2
peek1: 3
peek2: 3 (прошёл filter)
peek1: 4
peek2: 4
peek3: 4 (после skip)
peek1: 5
peek2: 5
peek3: 5 (после skip)
peek4: 5 (в limit)
result: 5
peek1: 6
peek2: 6
peek3: 6
peek4: 6 (в limit)
result: 6 (limit=2, получили 2 элемента, стоп)
Важная оптимизация: short-circuit операции
Некоторые операции требуют обработки всех элементов, другие — нет:
// Требует обработки ВСЕХnumbers
List<Integer> result = numbers.stream()
.filter(x -> x > 2)
.collect(Collectors.toList()); // Обработает все элементы
// Требует ВСЕ элементы для подсчёта
long count = numbers.stream()
.filter(x -> x > 2)
.count(); // Обработает все элементы
// НЕ требует обработку ВСЕх элементов (short-circuit)
boolean any = numbers.stream()
.filter(x -> x > 2)
.anyMatch(x -> x == 5); // Может остановиться на первом совпадении
// НЕ требует все элементы
Optional<Integer> first = numbers.stream()
.filter(x -> x > 2)
.findFirst(); // Остановится на первом найденном элементе
flatMap - особый случай
List<List<Integer>> matrix = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
matrix.stream()
.peek(list -> System.out.println("peek1: " + list))
.flatMap(list -> {
System.out.println("flatMap: " + list);
return list.stream();
})
.peek(x -> System.out.println("peek2: " + x))
.collect(Collectors.toList());
flatMap создаёт новый stream для каждого элемента исходного потока.
Когда промежуточные методы выполняются?
| Ситуация | Выполнение |
|---|---|
| Есть терминальная операция | ✅ Выполняются |
| Нет терминальной операции | ❌ НЕ выполняются |
| Есть stream но не сохранён | ❌ НЕ выполняются |
| Сохранён как переменная | ❌ НЕ выполняются (пока не вызвать terminal) |
Типы промежуточных операций
Stateless (без состояния)
Выполняют одинаково для каждого элемента:
filter()map()flatMap()peek()
Stateful (с состоянием)
Нужно знать информацию о других элементах:
distinct()— помнит все предыдущие элементыsorted()— должна знать все элементы перед сортировкойlimit()— считает количество элементовskip()— пропускает первые N элементов
// distinct требует буфера для отслеживания уникальности
numbers.stream()
.distinct() // stateful - должна помнить все элементы
.forEach(System.out::println);
// sorted требует всех элементов ДО сортировки
numbers.stream()
.sorted() // stateful - должна прочитать все перед выводом
.forEach(System.out::println);
Вывод
- Промежуточные методы ленивы — выполняются только с терминальной операцией
- Вертикальное выполнение — каждый элемент проходит весь pipeline
- Short-circuit операции — findFirst(), anyMatch() и т.д. могут остановиться раньше
- Stateful операции — distinct(), sorted() требуют буфера
- Без terminal операции — ничего не происходит (даже side-effects в peek())