— Привет, Амиго!

— Привет, Элли!

— Сегодня я хочу рассказать тебе про итераторы.

Итераторы придумали практически тогда, когда и коллекции. Основная задача коллекций была – хранить элементы, а основная задача итератора – выдавать эти элементы по одному.

— А что сложного в том, чтобы выдать набор элементов?

— Во-первых, некоторые коллекции, как например Set не имеют установленного порядка элементов и/или он постоянно меняется.

Во-вторых, некоторые структуры данных могут хранить объекты очень сложно: различными группами, списками и т.д. Т.е. задача отдать последовательно все элементыбудет сложной и нетривиальной.

В третьих – коллекции имеют свойство меняться. Решил ты вывести на экран все содержимое коллекции, а прямо в середине вывода JVM переключилась на другую нить, которая половину элементов из этой коллекции заменила на другую. Вот и получишь ты вместо вывода не пойми что.

— М-да.

— Вот! Именно такие проблемы должен был решить итератор. Итератор – это специальный внутренний объект в коллекции, который с одной стороны имеет доступ ко всем ее private данным и знает ее внутреннюю структуру, с другой – реализует общедоступный интерфейс Iterator, благодаря чему все знают, как с ним работать.

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

Думаю, ты уже сталкивался с тем, что при работе с for each нельзя одновременно «идти по коллекции циклом» и удалять из нее элементы. Это все именно из-за устройства итератора.

В новых коллекциях, добавленных в библиотеке concurrency, устройство итератора переработано, поэтому там такой проблемы нет.

Давай я тебе напомню, как устроен итератор.

В Java есть специальный интерфейс Iterator, вот какие у него методы:

Методы интерфейса Iterator<E> Описание
boolean hasNext() Проверяет, есть ли еще элементы
E next() Возвращает текущий элемент и переключается на следующий.
void remove() Удаляет текущий элемент

Итератор позволяет поочередно получить все элементы коллекции. Логичнее представить итератор чем-то вроде InputStream – у него есть все данные, но его задача выдавать их последовательно.

Метод next() возвращает следующий (очередной) элемент коллекции.

Метод hasNext() используется, чтобы проверять, есть ли еще элементы.

Ну, а remove() – удаляет текущий элемент.

Вопросы есть?

— А почему методы называются так странно? Почему не isEmpty() или getNextElement()?

Разве так не логичнее?

— Логичнее, но такие названия пришли из языка C++, где итераторы появились раньше.

— Ясно. Продолжим.

Кроме итератора есть еще интерфейс Iterable – его должны реализовывать все коллекции, которые поддерживают итератор. У него есть единственный метод:

Методы interface Iterable<T> Описание
Iterator<T>iterator() Возвращает объект-итератор

С помощью этого метода у любой коллекции можно получить объект итератор для обхода ее элементов. Давай обойдем все элементы дерева в коллекции TreeSet:

Пример
TreeSet<String> set = new TreeSet<String>();
Iterator<String> iterator = set.iterator();

while (iterator.hasNext())
{
 String item = iterator.next();
 System.out.println(item);
}

Такое использование итератора не очень удобно – слишком много лишнего и очевидного кода. Ситуация упростилась, когда в Java появился цикл по итератору – for-each.

Теперь такой код гораздо компактнее и читабельнее:

Было Стало
TreeSet<String> set = new TreeSet<String>();
Iterator<String> iterator = set.iterator();

while (iterator.hasNext())
{
 String item = iterator.next();
 System.out.println(item);
}
TreeSet<String> set = new TreeSet<String>();

for(String item : set)
{
 System.out.println(item);
}

Это один и тот же код! Итератор используется и там, и там.

Просто в цикле for-each его использование скрыто. Обрати внимание – в коде справа вообще нет красного цвета. Использование итератора скрыто полностью.

Цикл for-each можно использовать для любых объектов, которые поддерживают итератор. Т.е. ты можешь написать свой класс, добавить ему метод iterator() и сможешь использовать его объекты в правой части конструкции for-each.

— Ого! Я, конечно, не рвусь писать собственные коллекции и итераторы, но предложение все равно заманчивое. Возьму на карандаш.

— Кроме того, есть еще одна популярная разновидность итераторов, для которой даже придумали свой интерфейс. Речь идет об итераторе для списков – ListIterator.

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

Вот какие методы есть у интерфейса ListIterator<E>:

Метод Описание
boolean hasNext() Проверяет, есть ли еще элементы впереди.
E next() Возвращает следующий элемент.
int nextIndex() Возвращает индекс следующего элемента
void set(E e) Меняет значение текущего элемента
boolean hasPrevious() Проверяет, есть ли элементы позади.
E previous() Возвращает предыдущий элемент
int previousIndex() Возвращает индекс предыдущего элемента
void remove() Удаляет текущий элемент
void add(E e) Добавляет элемент в список.

Т.е. тут мы можем ходить не только вперед, но и назад. И еще пара фич по мелочи.

— Что ж, интересная штука. А где его используют?

— Например, ты хочешь двигаться туда-обратно по связному списку. При этом операция get будет довольно медленной, а операция next() очень быстрой.

— Хм. Убедила. Буду иметь ввиду.

Спасибо, Элли!