В каких случаях лучше использовать for-each или stream в Java?
Что проверяют
Для QA-автоматизатора это вопрос о читаемости тестового кода, а не о микрооптимизациях. Интервьюер смотрит, пишете ли вы стримы по делу или везде подряд, и следом часто спрашивает, почему в лямбде не компилируется вызов метода с checked-исключением.
Как отвечать
Разделите по задаче:
- stream: преобразовать, отфильтровать, собрать. В автотестах это типичный случай:
elements.stream().map(WebElement::getText).toList()для текстов элементов илиanyMatch/allMatchдля проверки условия. Вместе с AssertJ получается короткая читаемая проверка.Stream.toList()появился в Java 16 и возвращает неизменяемый список; - for-each: когда внутри побочные действия и управление потоком. Нужен ранний выход через
breakилиreturn, нужно менять локальную переменную (в лямбде она должна быть effectively final), метод бросает checked-исключение, каждое действие нужно залогировать или обернуть в шаг Allure; - отладка: в цикле удобнее ставить брейкпоинт, а стек падения из длинной цепочки стрима забит внутренними вызовами;
- производительность: на объёмах тестовых данных разница незаметна. У стрима есть небольшие накладные расходы, но решать стоит по читаемости.
Отдельно про parallelStream(): в UI-тестах его не используют. WebDriver не потокобезопасен, и параллельные обращения к одному драйверу дают нестабильные падения. Параллельность настраивают на уровне раннера (JUnit 5, TestNG) с отдельным драйвером на поток.
Где спотыкаются
Говорят, что стримы быстрее, потому что «работают параллельно». Обычный stream() последовательный, а параллельный на маленьких коллекциях чаще медленнее.
Удаляют элементы из коллекции внутри for-each и получают ConcurrentModificationException. Правильно: removeIf или явный Iterator с remove().
Пишут побочные эффекты в map или peek. С Java 9 count() может вообще не выполнять конвейер, если размер известен из источника, и логирование в peek молча пропадёт.