Что лучше с точки зрения производительности: стримы или циклы for в Java
Что проверяют
Умеете ли вы рассуждать о производительности числами, а не ощущениями, и понимаете ли, где в реальном коде находится узкое место. Категоричный ответ в любую сторону здесь и есть ошибка.
Как отвечать
Начать с главного: в прикладном коде разница между стримом и циклом тонет на фоне похода в базу или в сеть, поэтому выбор делается по читаемости. Дальше по существу:
- на горячем пути и на примитивах цикл обычно быстрее:
Stream<Integer>упаковывает каждое число в объект, а это аллокации. Если уж стрим, тоIntStreamиLongStream; - у стрима есть постоянная цена сборки конвейера, поэтому на коллекции из нескольких элементов он проигрывает кратно в относительных числах и незаметно в абсолютных;
- JIT хорошо разворачивает простые циклы, а лямбда, используемая из нескольких мест, становится мегаморфной, и встраивание ломается;
- стрим выигрывает ленивостью:
findFirst,anyMatchиlimitне обходят коллекцию целиком, а вручную это пишется с флагами иbreak; parallelStream()окупается на десятках тысяч элементов и на хорошо делимом источнике (массив,ArrayList); связный список и потоковые источники делятся плохо.
Закончить тем, что спор решается замером на JMH, а не рассуждением.
Где спотыкаются
Меряют разницу в обычном методе main без прогрева и делают вывод по первому запуску. До того как JIT скомпилирует горячий код, числа не значат ничего, и стрим на таком замере проигрывает в разы.
Второе: ставят parallelStream() везде. По умолчанию он работает в общем пуле ForkJoin, одном на всё приложение, и блокирующая операция внутри останавливает всех остальных его пользователей.
Третье: пишут в общую коллекцию из forEach. Побочные эффекты в конвейере не потокобезопасны, в параллельном режиме элементы теряются, и результат нужно собирать через collect.