В чем разница между Iterator и forEach из Stream API в Java?

Ответ

Iterator — это классический паттерн-объект для обхода коллекций. forEach() в Stream API — это терминальная операция, выполняющая действие для каждого элемента потока. Их ключевые различия — в парадигме и возможностях.

Сравнение:

Критерий Iterator (из java.util) forEach() (из java.util.stream.Stream)
Парадигма Императивное программирование (как делать). Декларативное/функциональное программирование (что делать).
Управление Вы явно управляете итерацией (hasNext(), next()), можете прервать её. Итерация управляется внутренне, прервать нельзя (кроме исключения).
Модификация коллекции Позволяет удалять элементы во время обхода с помощью iterator.remove(). Запрещает модификацию источника во время выполнения (может вызвать ConcurrentModificationException).
Параллелизм Последовательный обход. Легко распараллеливается вызовом .parallelStream().forEach(...).
Порядок Гарантирует порядок для упорядоченных коллекций. В параллельном потоке порядок не гарантирован.

Примеры:

List<String> list = Arrays.asList("A", "B", "C");

// 1. Iterator (императивный стиль, можно удалить элемент)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String elem = it.next();
    if ("B".equals(elem)) {
        it.remove(); // Удаление возможно, если коллекция mutable
    }
    System.out.print(elem); // A B C
}

// 2. forEach() из Stream API (функциональный стиль)
list.stream()
    .forEach(elem -> System.out.print(elem)); // A B C
// list.stream().forEach(elem -> if("B".equals(elem)) list.remove(elem)); // Выбросит исключение!

// 3. Параллельное выполнение forEach
list.parallelStream()
    .forEach(e -> System.out.print(e)); // Порядок вывода не гарантирован (например, C A B)

Важное замечание: Также существует метод forEach() прямо в интерфейсе Iterable (например, list.forEach(...)), который похож на Stream.forEach(), но работает с коллекцией напрямую и также не гарантирует порядок в параллельных коллекциях.

Когда что использовать:

  • Iterator: Когда нужен полный контроль над итерацией (пропуск, удаление элементов, сложная логика обхода).
  • Stream.forEach(): В функциональных цепочках обработки данных (filter, map, etc.), когда важна декларативность и потенциальный параллелизм, и модификация коллекции не требуется.

Ответ 18+ 🔞

Давай разберём эту дичь, чтобы даже кошка, проходящая мимо монитора, поняла, в чём разница. Сиди, слушай, не дергайся.

Вот представь: у тебя есть мешок картошки. Iterator — это ты сам, который суёшь руку в мешок, нащупываешь каждую картофелину (hasNext()), вытаскиваешь её (next()), можешь посмотреть, гнилая ли она, и если гнилая — выкинуть нахуй обратно в огород (remove()). Весь процесс — под твоим контролем. Захотел — пропустил мелкую, захотел — остановился, потомусть устал. Императивщина, блядь. Делай всё сам.

А теперь forEach() из Stream API — это ты нанял бригаду китайцев-роботов, дал им тот же мешок и сказал: «Ребята, на каждую картошку из этого мешка наклейте стикер „проверено“». Ты не лезешь в мешок, ты даже не знаешь, в каком порядке они их там хватают, особенно если роботов несколько (parallelStream()). Ты просто объявил (декларативно, ёпта!), ЧТО нужно сделать. А КАК они это сделают — их проблемы. Попробуй во время этого конвейера сунуть руку в мешок и выкинуть гнилую картошку — вся бригада впадет в ступор и наорет ConcurrentModificationException тебе в ебало. Прервать их тоже не попросишь — только если кирпич в движок бросить (исключение выкинуть).

Короче, таблица, чтобы в голове не еблось:

Что сравниваем Iterator (старая школа) Stream.forEach() (моднявый функциональщик)
Философия «Дай сюда, я сам всё сделаю, я ж не хуй собачий!» «Ребята, сделайте вот это, а я пойду пивка куплю».
Контроль Полный. Могу остановиться, когда захочу. Нулевой. Запустил — иди гуляй, пока всё не сделают.
Можно ли выкинуть картошку? Да, можно (remove()). Ни в коем случае! Сломаешь весь конвейер.
Скорость на больших объемах Медленно, но верно, одна рука. Можно кинуть десять роботов (parallelStream) — будет быстрее, но порядок ебётся.
Порядок Как в мешке лежали, так и достаются. В параллельном режиме — хрен его знает, что первым выловят.

Примеры, чтобы окончательно добить:

List<String> list = Arrays.asList("Арбуз", "Банан", "Хуй с горы");

// 1. Iterator — делаем всё вручную, как деды воевали
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String elem = it.next();
    if (elem.contains("Хуй")) {
        System.out.println("Нашёл! Но удалить не могу, список immutable... эх.");
    }
    System.out.print(elem + " "); // Арбуз Банан Хуй с горы
}

// 2. Stream.forEach() — расслабься и получай удовольствие
list.stream()
    .forEach(elem -> System.out.print(elem + " ")); // Арбуз Банан Хуй с горы
// Попытка модификации — пиздец и ConcurrentModificationException:
// list.stream().forEach(e -> list.remove(e)); // В РОТ ТЕБЕ ЧИХ-ПЫХ, НЕЛЬЗЯ!

// 3. Параллельный разгул — порядок пошёл нахуй
list.parallelStream()
    .forEach(e -> System.out.print(e + " ")); // Может вывести "Хуй с горы Банан Арбуз"

Итог, блядь:

  • Iterator — когда тебе надо контролировать процесс, что-то пропускать, удалять или у тебя сложная, ебучная логика обхода. Старая, добрая, императивная канитель.
  • Stream.forEach() — когда у тесть простой конвейер (отфильтровал -> преобразовал -> вывел), и тебе похуй на детали, лишь бы работало быстро, особенно если данных овердохуища. Функциональный подход, за которым будущее, но с ограничениями, как у мартышлюшки в клетке.

Выбирай по ситуации, а не потому, что один метод «красивее». Всё, вопрос закрыт, можно расходиться.