В чем разница ленивых и жадных стримов в Java
Что проверяют
Понимаете ли вы, что в Java нет двух видов стримов: ленивость и жадность это свойства операций внутри одного конвейера. Следом обычно спрашивают, в каком порядке выполнится цепочка с peek() или что будет, если не вызвать терминальную операцию.
Как отвечать
Стрим строится из источника, промежуточных операций и одной терминальной:
- промежуточные (
filter,map,flatMap,sorted,distinct,limit,peek): ленивые. Они только добавляют этап в описание конвейера и сразу возвращают новыйStream, ни один элемент при этом не читается; - терминальные (
collect,forEach,reduce,count,findFirst,anyMatch): запускают весь конвейер. После них стрим израсходован, повторный вызов дастIllegalStateException; - короткое замыкание:
findFirst,anyMatch,limitостанавливают обработку, как только результат известен. ПоэтомуStream.iterate(1, x -> x + 1).filter(...).findFirst()на бесконечном источнике завершается.
Важная деталь: элементы идут по конвейеру по одному, вертикально. Первый элемент проходит filter, map и доходит до терминальной операции, и только потом берётся второй. Исключение составляют операции с состоянием: sorted обязан прочитать весь вход, прежде чем отдать первый элемент, и на бесконечном стриме зависнет.
Если спрашивают про «жадный» вариант, честно скажите, что это обычные операции над коллекциями: List.removeIf, replaceAll или цикл выполняются сразу и целиком, с промежуточными коллекциями на каждом шаге. Для сравнения можно упомянуть Kotlin, где разница явная: List жадный, Sequence ленивый.
Где спотыкаются
Называют filter ленивым, а collect жадным и на этом останавливаются, не объясняя, что именно откладывается. Интервьюер ждёт слов про поэлементную обработку и короткое замыкание.
Кладут побочные эффекты в peek или map и удивляются, что без терминальной операции ничего не напечаталось. А начиная с Java 9 count() может вообще не проходить по элементам, если размер известен из источника, и peek не сработает даже с терминальной операцией.
Пытаются переиспользовать сохранённый в переменную стрим. Нужен новый вызов stream() или Supplier<Stream<T>>.