Какой функциональный интерфейс принимает метод `forEach()` в Java Stream API?

«Какой функциональный интерфейс принимает метод `forEach()` в Java Stream API?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Java Stream API метод forEach() также принимает функциональный интерфейс Consumer<T>.

Ключевые отличия от forEach() в Iterable:

  • Порядок выполнения: В параллельных стримах (parallelStream()) порядок обработки элементов не гарантирован.
  • Потокобезопасность: Для корректной работы в параллельном режиме Consumer должен быть потокобезопасным или не иметь разделяемого изменяемого состояния.

Примеры в Stream API:

List<String> names = List.of("Alice", "Bob", "Charlie");

// 1. Последовательный стрим
names.stream()
     .forEach(System.out::println); // Порядок гарантирован: Alice, Bob, Charlie

// 2. Параллельный стрим
names.parallelStream()
     .forEach(System.out::println); // Порядок вывода не гарантирован!

// 3. Consumer с состоянием (опасно для parallelStream!)
List<String> result = Collections.synchronizedList(new ArrayList<>());
names.parallelStream()
     .forEach(result::add); // Требуется синхронизированная коллекция

Рекомендация: Для операций с побочными эффектами в стримах, где важен порядок или нужна мутация состояния, часто лучше использовать forEachOrdered() или коллектор (collect()), а не forEach().