Iterator и Iterable

В Java обход коллекций унифицирован через два интерфейса:

  • Iterable<E> — «то, что можно обойти». Содержит единственный обязательный метод iterator(). Любой класс, реализующий Iterable, можно использовать в цикле for-each.

  • Iterator<E> — сам обходчик. Методы: hasNext(), next(), remove().

Collection наследует Iterable, поэтому все коллекции (List, Set, Queue) поддерживают for-each. Map — не Iterable, но через keySet()/values()/entrySet() тоже легко обходится.

ListIterator<E> — расширение для списков с двусторонним обходом и заменой элементов.

Контракт Iterator

interface Iterator<E> {
    boolean hasNext();
    E       next();           // бросает NoSuchElementException, если элементов нет
    void    remove();         // опционально: удаляет последний возвращённый next()
}

Пример 1. Явный Iterator

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorBasic {
    public static void main(String[] args) {
        List<String> kits = new ArrayList<>();
        kits.add("UNO");
        kits.add("ESP32");
        kits.add("Phobo");

        Iterator<String> it = kits.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

Output:

UNO
ESP32
Phobo

Пример 2. Безопасное удаление через Iterator.remove

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class IteratorRemove {
    public static void main(String[] args) {
        List<Integer> values = new ArrayList<>(Arrays.asList(10, 25, 7, 42, 3));
        Iterator<Integer> it = values.iterator();
        while (it.hasNext()) {
            if (it.next() < 10) {
                it.remove();
            }
        }
        System.out.println(values);
    }
}

Output:

[10, 25, 42]

Пример 3. fail-fast поведение

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ConcurrentModificationException;

public class IteratorFailFast {
    public static void main(String[] args) {
        List<String> data = new ArrayList<>();
        data.add("a"); data.add("b"); data.add("c");

        try {
            for (String s : data) {
                if (s.equals("b")) {
                    data.remove(s); // структурное изменение во время обхода
                }
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("Поймано: ConcurrentModificationException");
        }

        // Правильный способ:
        Iterator<String> it = data.iterator();
        while (it.hasNext()) {
            if (it.next().equals("b")) it.remove();
        }
        System.out.println(data);
    }
}

Output:

Поймано: ConcurrentModificationException
[a, c]

Пример 4. ListIterator — двусторонний обход

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

public class IteratorListIter {
    public static void main(String[] args) {
        List<Integer> values = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
        ListIterator<Integer> it = values.listIterator();

        while (it.hasNext()) {
            int v = it.next();
            it.set(v * 10);
        }
        System.out.println("Вперёд: " + values);

        StringBuilder sb = new StringBuilder("Назад:");
        while (it.hasPrevious()) {
            sb.append(' ').append(it.previous());
        }
        System.out.println(sb);
    }
}

Output:

Вперёд: [10, 20, 30, 40]
Назад: 40 30 20 10

Пример 5. Свой Iterable

import java.util.Iterator;
import java.util.NoSuchElementException;

public class IteratorCustom {
    static class Range implements Iterable<Integer> {
        private final int from, to;
        Range(int from, int to) { this.from = from; this.to = to; }

        @Override
        public Iterator<Integer> iterator() {
            return new Iterator<>() {
                int cur = from;
                @Override public boolean hasNext() { return cur < to; }
                @Override public Integer next() {
                    if (!hasNext()) throw new NoSuchElementException();
                    return cur++;
                }
            };
        }
    }

    public static void main(String[] args) {
        for (int v : new Range(2, 7)) {
            System.out.print(v + " ");
        }
        System.out.println();
    }
}

Output:

2 3 4 5 6

Пример 6. forEach и Stream

import java.util.Arrays;
import java.util.List;

public class IteratorForEach {
    public static void main(String[] args) {
        List<String> cmds = Arrays.asList("F", "L", "R", "S");
        cmds.forEach(c -> System.out.println("cmd=" + c));
        long count = cmds.stream().filter(c -> !c.equals("S")).count();
        System.out.println("Движений: " + count);
    }
}

Output:

cmd=F
cmd=L
cmd=R
cmd=S
Движений: 3

Подводные камни

  • Изменение коллекции во время for-each без Iterator.remove() — почти всегда ConcurrentModificationException.

  • Iterator.remove() можно вызывать только один раз на каждый next().

  • Iterator одноразовый — после прохода создавайте новый.

  • Не все Iterator поддерживают remove() — некоторые бросают UnsupportedOperationException (например, для неизменяемых коллекций).

  • В многопоточном коде fail-fast не гарантирует обнаружение всех ошибок; используйте CopyOnWriteArrayList или явную синхронизацию.

См. также

Примечание

Материал подготовлен на основе официального руководства Oracle The Collections Framework и распространяется на условиях Oracle Free Documentation License. Тексты переписаны своими словами, примеры кода — оригинальные.