Вопрос 24 · Раздел 8

Как работает короткое замыкание (short-circuiting) в Stream?

У Sink есть метод cancellationRequested():

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Short-circuiting (короткое замыкание) — стрим останавливается, как только результат определён, не обрабатывая оставшиеся элементы.

Аналогия: как && в Java: false && expensiveCheck()expensiveCheck() не выполнится, потому что результат уже определён (false). Аналог break в цикле.

Terminal операции с коротким замыканием:

  • anyMatch() — останавливается при первом true
  • allMatch() — останавливается при первом false
  • noneMatch() — останавливается при первом true
  • findFirst() / 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()]]