Iterable

Iterable<T> — источник итератора. Его реализация позволяет использовать объект справа от for-each.

public interface Iterable<T> {
    Iterator<T> iterator();
    default void forEach(Consumer<? super T> action) { /* ... */ }
    default Spliterator<T> spliterator() { /* ... */ }
}

Iterable, Iterator и for-each

Iterable создаёт обход, а Iterator хранит состояние одного обхода. Каждый вызов iterator() обычно возвращает новый независимый объект.

for (String item : names) {
    use(item);
}

Для Iterable компилятор превращает это примерно в:

for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
    String item = it.next();
    use(item);
}

Массивы также поддерживают for-each, хотя не реализуют Iterable.

Контракт Iterator

  • hasNext() сообщает, есть ли следующий элемент, и не сдвигает позицию;
  • next() возвращает элемент и сдвигает позицию;
  • next() после конца бросает NoSuchElementException;
  • remove() — необязательная операция и удаляет последний выданный элемент.
final class Range implements Iterable<Integer> {
    private final int from;
    private final int to;
 
    Range(int from, int to) {
        this.from = from;
        this.to = to;
    }
 
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            private int current = from;
 
            public boolean hasNext() {
                return current < to;
            }
 
            public Integer next() {
                if (!hasNext()) throw new NoSuchElementException();
                return current++;
            }
        };
    }
}

Изменение во время обхода

У многих коллекций iterator fail-fast: внешнее структурное изменение может привести к ConcurrentModificationException. Корректное удаление в однопоточном обходе выполняют через Iterator.remove() или removeIf. Это best-effort проверка, не механизм синхронизации.

Spliterator дополнительно умеет делить источник на части и описывать его свойства. На нём строится создание последовательных и параллельных stream.

Вопросы для собеседования

  1. Чем Iterable отличается от Iterator? Первый создаёт обход, второй хранит позицию конкретного обхода.
  2. Можно ли повторно использовать Iterator? После исчерпания — обычно нет; получают новый через iterator().
  3. Что обязан сделать next после конца? Бросить NoSuchElementException.
  4. Почему массив работает в for-each? Для массивов это отдельное правило языка.
  5. Как удалять при обходе? Через iterator или специальные методы коллекции.
  6. Гарантирован ли ConcurrentModificationException? Нет, это best effort.
  7. Зачем Spliterator? Для разбиения обхода и передачи характеристик stream.