← Назад к вопросам

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

2.0 Middle🔥 171 комментариев
#Stream API и функциональное программирование#Коллекции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Iterator vs forEach из Stream API

Это два разных способа итерирования по коллекциям, с различными характеристиками, производительностью и случаями применения.

Iterator — классический способ

// Iterator работает со всеми Collection
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// Способ 1: через Iterator напрямую
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
    String name = iterator.next();
    System.out.println(name);
}

// Способ 2: for-each loop (использует Iterator под капотом)
for (String name : names) {
    System.out.println(name);
}

Характеристики:

  • ✅ Работает со всеми Collection (List, Set, Queue, etc)
  • ✅ Можно удалять элементы во время итерации (iterator.remove())
  • ✅ Позволяет контролировать итерацию (hasNext, next)
  • ✅ Нет внешних зависимостей
  • ❌ Imperative стиль (как делать)
  • ❌ Нельзя распараллелить
  • ❌ Нельзя использовать с ленивыми вычислениями

forEach из Stream API (Java 8+)

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

// forEach как метод Stream
names.stream()
    .forEach(System.out::println);

// Или прямо на Collection (через default метод)
names.forEach(System.out::println);

// Параллельный Stream
names.parallelStream()
    .forEach(System.out::println);

Характеристики:

  • ✅ Декларативный стиль (что делать, не как)
  • ✅ Встроенная поддержка параллелизма (parallelStream)
  • ✅ Ленивые вычисления (lazy evaluation)
  • ✅ Функциональный стиль программирования
  • ❌ Нельзя удалять элементы во время итерации
  • ❌ Нельзя прервать итерацию (break/continue)
  • ❌ Нельзя контролировать индекс

Сравнительная таблица

ХарактеристикаIteratorStream.forEach
СтильImperativeDeclarative/Functional
Контроль итерации✅ Полный (hasNext, next)❌ Нет (только перебор)
Удаление элементов✅ iterator.remove()❌ Нельзя
Прерывание (break)✅ Да❌ Нет
Индекс элемента❌ Нет❌ Нет (используй IntStream)
Параллелизм❌ Нет✅ parallelStream()
Ленивые вычисления❌ Нет✅ Да (intermediate operations)
Совместимость✅ Все Collection✅ Streams (от Collections и источников)
Производительность⚡ Быстро⚡⚡ Может быть быстрее с параллельной обработкой

Примеры использования

Iterator — когда нужен контроль

// 1. Удаление элементов во время итерации
List<String> items = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
Iterator<String> iter = items.iterator();
while (iter.hasNext()) {
    String item = iter.next();
    if (item.equals("B")) {
        iter.remove();  // Безопасно удаляет элемент
    }
}
// items: [A, C, D]

// 2. Условное прерывание итерации
Iterator<String> iterator = items.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("stop")) break;  // Можно прервать
    System.out.println(item);
}

// 3. Работа с индексом (через ручной счётчик)
Iterator<String> iter = items.iterator();
int index = 0;
while (iter.hasNext()) {
    System.out.println(index + ": " + iter.next());
    index++;
}

Stream.forEach — когда нужна функциональность

List<User> users = Arrays.asList(new User(1), new User(2));

// 1. Функциональный стиль с трансформациями
users.stream()
    .filter(u -> u.getAge() > 18)
    .map(User::getName)
    .forEach(System.out::println);

// 2. Параллельная обработка (очень быстро на больших данных)
users.parallelStream()
    .filter(u -> expensiveOperation(u))
    .forEach(System.out::println);

// 3. Ленивые вычисления (stop при first match)
users.stream()
    .filter(u -> u.getAge() > 18)
    .limit(1)  // Остановится после первого элемента
    .forEach(System.out::println);

// 4. Комплексная обработка
users.stream()
    .collect(Collectors.groupingBy(User::getCity))
    .forEach((city, cityUsers) -> 
        System.out.println(city + ": " + cityUsers.size())
    );

Важное различие: Default forEach

// Java 8 добавила default метод forEach в Iterable
public interface Iterable<T> {
    Iterator<T> iterator();
    
    default void forEach(Consumer<? super T> action) {
        for (T t : this) {
            action.accept(t);
        }
    }
}

// Это НЕ Stream API! Это просто удобный способ:
List<String> names = Arrays.asList("Alice", "Bob");
names.forEach(System.out::println);  // ← Это default forEach из Iterable
// Эквивалентно:
for (String name : names) {
    System.out.println(name);
}

Stream.forEach vs for-each vs Iterator.forEach

List<String> names = Arrays.asList("Alice", "Bob");

// Все три примера почти эквивалентны (но не совсем):

// 1. Stream.forEach()
names.stream().forEach(System.out::println);

// 2. Iterable.forEach() (default метод)
names.forEach(System.out::println);

// 3. For-each loop (использует Iterator)
for (String name : names) {
    System.out.println(name);
}

// 4. Iterator напрямую
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

Производительность

// Для маленьких коллекций (< 1000 элементов)
for (int i = 0; i < 1000000; i++) {
    list.stream().forEach(x -> x++);  // Медленнее (создание Stream)
    list.forEach(x -> x++);           // Среднее
    for (int j = 0; j < list.size(); j++) {}  // Быстрее
}

// Для больших коллекций на многоядерных системах
largeList.parallelStream().forEach(expensiveOperation());  // ОЧЕНЬ БЫСТРО!

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

Используй Iterator если:

  • ✅ Нужно удалять элементы во время итерации
  • ✅ Нужен контроль над итерацией (break, условия)
  • ✅ Нужен максимальный контроль
  • ✅ Работаешь с собственной итератор-логикой

Используй Stream.forEach если:

  • ✅ Нужны трансформации и фильтрация данных
  • ✅ Нужна параллельная обработка
  • ✅ Хочешь функциональный, декларативный стиль
  • ✅ Предпочитаешь чистоту кода

Используй Iterable.forEach если:

  • ✅ Просто перебираешь коллекцию без трансформаций
  • ✅ Хочешь простоту и читаемость
  • ✅ Не нужны потоки или параллельная обработка

Практический пример из реальности

// Обработка больших файлов
try (Stream<String> lines = Files.lines(file)) {
    lines.filter(l -> !l.isEmpty())
         .map(String::trim)
         .filter(l -> l.startsWith("#"))
         .parallelStream()  // Параллельная обработка
         .forEach(this::processLine);
}