— Привет, Амиго!
— Привет, Элли!
— Сегодня я хочу рассказать тебе про итераторы.
Итераторы придумали практически тогда, когда и коллекции. Основная задача коллекций была – хранить элементы, а основная задача итератора – выдавать эти элементы по одному.
— А что сложного в том, чтобы выдать набор элементов?
— Во-первых, некоторые коллекции, как например 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.
Теперь такой код гораздо компактнее и читабельнее:
Было | Стало |
---|---|
|
|
Это один и тот же код! Итератор используется и там, и там.
Просто в цикле 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() очень быстрой.
— Хм. Убедила. Буду иметь ввиду.
Спасибо, Элли!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ