Iterable и Iterator в Java: объект для обхода и состояние обхода
💻 Информатика · 10 класс
HasNext, next, remove и enhanced for: как проходить коллекцию и не удалять элементы небезопасно.

Два интерфейса решают разные задачи
Iterable<T> обозначает объект, по элементам которого можно организовать обход. Его метод iterator() возвращает Iterator<T>. Iterator хранит состояние конкретного прохода: какие элементы уже получены и есть ли следующий.
Такое разделение позволяет получать разные итераторы для одного объекта. Но обещание независимости и порядка нужно проверять у конкретной реализации. Сам интерфейс Iterable не означает, что данные обязательно хранятся в массиве или поддерживают индексный доступ.
Пример обхода
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
List<String> words = new ArrayList<>();
words.add("sun");
words.add("map");
Iterator<String> cursor = words.iterator();
while (cursor.hasNext()) {
String word = cursor.next();
System.out.println(word);
}Фрагмент внутри метода выводит sun и map. HasNext проверяет наличие следующего элемента, но не извлекает его. Next возвращает элемент и продвигает состояние. Если вызвать next после завершения, ожидается NoSuchElementException.
Связь с расширенным for
Для объекта Iterable запись for (String word : words) организует аналогичный обход через итератор. Массивы тоже поддерживают расширенный for, хотя не являются объектами, реализующими Iterable. Поэтому правило «всё, что проходит for-each, обязано иметь iterator()» неверно для массивов.
Если нужен только последовательный просмотр, расширенный цикл обычно короче. Явный Iterator полезен, когда необходимо управлять проходом или использовать поддерживаемое удаление текущего элемента.
Удаление через итератор
Iterator<String> cursor = words.iterator();
while (cursor.hasNext()) {
if (cursor.next().length() == 3) {
cursor.remove();
}
}
System.out.println(words.size());Для созданного выше изменяемого ArrayList результат —0:оба слова содержат три символа. Remove удаляет последний элемент, возвращённый next, а не будущий. Его можно вызывать не более одного раза после каждого next.
Remove — необязательная операция интерфейса. У неподдерживающей реализации она может выбросить UnsupportedOperationException. Нельзя обещать, что любой Iterator позволяет изменять коллекцию.
Параллельные изменения
Прямое изменение базовой коллекции во время обхода обычно нельзя делать без знания её контракта. Некоторые реализации обнаруживают изменение и выбрасывают ConcurrentModificationException, но полагаться на такую диагностику как на гарантию безопасности нельзя.
Не удаляйте элемент через words.remove в расширенном цикле только потому, что в маленьком примере это случайно не вызвало сбой. Для задачи выбирают Iterator.remove, removeIf или специально предназначенную конкурентную коллекцию.
Разобранная трассировка
До первого next ни один элемент ещё не возвращён. Два вызова hasNext подряд не пропускают sun. Первый next выдаёт sun, второй — map. После них hasNext становится false. Таким образом, проверка и продвижение — разные операции.
Практика
- Кто возвращает iterator(): Iterable или Iterator?
- Продвигает ли hasNext обход?
- Можно ли вызвать remove дважды после одного next?
Ответы
1. Iterable. 2. Нет: продвижение выполняет next. 3. Нет: нарушается контракт, ожидается IllegalStateException. Даже корректный порядок вызовов нужно сочетать с проверкой того, поддерживает ли реализация удаление вообще.