Как работает короткое замыкание (short-circuiting) в Stream?
У Sink есть метод cancellationRequested():
🟢 Junior Level
Short-circuiting (короткое замыкание) — стрим останавливается, как только результат определён, не обрабатывая оставшиеся элементы.
Аналогия: как && в Java: false && expensiveCheck() — expensiveCheck() не выполнится, потому что результат уже определён (false). Аналог break в цикле.
Terminal операции с коротким замыканием:
anyMatch()— останавливается при первомtrueallMatch()— останавливается при первомfalsenoneMatch()— останавливается при первомtruefindFirst()/findAny()— останавливается при первом элементеlimit(n)— останавливается после n элементов
// Остановится после первого найденного
boolean hasAdult = persons.stream()
.anyMatch(p -> p.getAge() >= 18);
// Возьмёт только первые 5
List<String> first5 = list.stream().limit(5).collect(toList());
🟡 Middle Level
Внутренний механизм: флаг cancellationRequested
У Sink есть метод cancellationRequested():
- Terminal операции выставляют его в
trueпосле нахождения результата - Intermediate операции проверяют флаг перед запросом следующего элемента
- Spliterator прекращает обход данных
Виды короткого замыкания
Java 9+:
count()на SIZED источниках (ArrayList, массив) оптимизирован — возвращает размер без прогона через пайплайн. Это значит, что filter/map ДО count() могут НЕ выполниться!
Terminal:
Останавливают весь конвейер при достижении условия (anyMatch, findFirst)
Intermediate:
Ограничивают объем данных (limit(n)) — превращает бесконечный стрим в конечный
Работа с бесконечными стримами
Stream.iterate(0, n -> n + 1) // Бесконечно
.filter(n -> n % 2 == 0)
.limit(10) // Ограничивает
.collect(toList()); // Завершается успешно
Важно: Если переставить filter и limit неправильно — можно получить бесконечный цикл.
🔴 Senior Level
Параллелизм и Short-circuiting
В параллельных стримах работает менее эффективно:
- Несколько потоков могут продолжать вычисления после нахождения результата
limit()требует сложной координации (буферизация из разных потоков)- Параллельный стрим может стать медленнее последовательного
Wait Cost
В распределенных системах findAny() предпочтительнее findFirst() — возвращает результат от самого “быстрого” потока, не дожидаясь элементов из очереди.
Infinite loop detection
Если CPU загружен на 100% и стрим не завершается — проверьте условия короткого замыкания. Добавьте peek() перед short-circuit операцией — увидите, сколько элементов реально проверено.
Short-circuit vs Loops
Короткое замыкание в стримах работает так же эффективно, как break или return в обычном цикле for.
🎯 Шпаргалка для интервью
Обязательно знать:
- Short-circuiting — стрим останавливается, как только результат определён, не обрабатывая оставшиеся элементы
- Terminal short-circuit: anyMatch, allMatch, noneMatch, findFirst, findAny
- Intermediate short-circuit: limit(n) — превращает бесконечный стрим в конечный
- Механизм: флаг
cancellationRequested()в Sink, Spliterator прекращает обход - В parallelStream short-circuit работает менее эффективно — несколько потоков могут продолжать вычисления
findAny()предпочтительнееfindFirst()в параллельных стримах — не ждёт «самый левый» элемент- anyMatch/limit — ключевые инструменты для работы с бесконечными стримами
- Java 9+: count() на SIZED источниках оптимизирован — filter/map ДО count() могут НЕ выполниться
Частые уточняющие вопросы:
- Как работает короткое замыкание внутри? — Terminal операция выставляет
cancellationRequested = true, Spliterator прекращает запрашивать элементы. - Почему findAny быстрее findFirst в parallelStream? — findFirst требует координации потоков для гарантии порядка; findAny возвращает результат от самого быстрого потока.
- Может ли limit() работать с бесконечным стримом? — Да, limit(n) — единственный способ завершить бесконечный стрим, но он должен стоять до тяжёлых операций.
- Почему parallelStream может стать медленнее при short-circuit? — Несколько воркеров продолжают работу после нахождения результата, оверхед на отмену задач.
Красные флаги (НЕ говорить):
- «Short-circuit обрабатывает все элементы» — неверно, в этом и суть короткого замыкания
- «findFirst и findAny одинаковы в parallelStream» — неверно, findFirst дорогая из-за координации
- «limit() после sorted() — хорошая идея» — неверно, сортировка всего набора перед ограничением избыточна
- «parallelStream всегда быстрее для short-circuit» — неверно, может быть медленнее из-за координации
Связанные темы:
- [[21. Что такое lazy evaluation в Stream]]
- [[22. Когда начинается выполнение операций в Stream]]
- [[25. Что такое операции anyMatch(), allMatch(), noneMatch()]]
- [[26. Что делают операции findFirst() и findAny()]]