Работали ли вы со Stream API в Java?
Что проверяют
Умеете ли вы писать декларативный код и понимаете ли, как он исполняется: ленивость промежуточных операций, одноразовость стрима, поведение в параллельном режиме. Следом идёт неудобное уточнение: «когда обычный цикл лучше» или «почему parallelStream не стоит ставить по умолчанию».
Как отвечать
Объясните устройство конвейера:
- источник: коллекция, массив,
Files.lines,IntStream.range; - промежуточные: ленивые, возвращают стрим (
filter,map,flatMap,sorted,distinct,takeWhile); пока нет терминальной операции, не выполняется ничего; - терминальные:
collect,toList,reduce,anyMatch,count; после них стрим израсходован, повторный вызов даётIllegalStateException; - сборка результата:
Collectors.groupingByс вложенным сборщиком (counting,mapping),partitioningBy,joining,teeingдля двух агрегатов за один проход.
Современные детали покажут, что вы пишете на актуальной Java: stream().toList() с шестнадцатой версии возвращает неизменяемый список; примитивные IntStream и LongStream избавляют от упаковки; Stream.gather с Gatherers из Java 24 даёт свою промежуточную операцию, например скользящее окно.
Про параллельные потоки говорите осторожно. Они работают на общем ForkJoinPool.commonPool, выигрывают только на больших объёмах, делимом источнике вроде массива и независимых дешёвых операциях, а блокирующий вызов внутри занимает общий пул всего приложения. Для конкурентного ввода-вывода правильный ответ это виртуальные потоки из Java 21.
Где спотыкаются
Прячут побочные эффекты в peek. Он предназначен для отладки, и его вызов не гарантирован: count() над конвейером, который не меняет размер, может вовсе не выполнить операции.
Ставят parallelStream() там, где это бессмысленно: список из двадцати элементов, LinkedList как источник, поход в базу внутри лямбды. Разбиение и слияние съедают выигрыш, а то и роняют пропускную способность приложения.
Собирают в Collectors.toMap без функции слияния и получают IllegalStateException на первом дубликате ключа. Он же кидает NullPointerException на null в значении, в отличие от обычного HashMap.