Зачем в Java 2 интерфейса: Iterable и Iterator?
По-моему они только вносят путаницу. Я так понял их роли: Iterable говорит что по объектам класса в принципе можно итерироваться, а Iterator задает сами методы для итерирования. Но по-моему все это отлично смотрелось бы в одном интерфейсе. Который и объявлял бы класс итерируемым и одновременно задавал бы методы для итерирования.
Отслеживать
задан 8 фев 2016 в 6:30
Александр Елизаров Александр Елизаров
2,788 2 2 золотых знака 18 18 серебряных знаков 36 36 бронзовых знаков
Иногда приходится решать задачи, требующие реализации своих итераторов и Iterable. Вот пример не совсем стандартной работы с коллекциями, здесь нужно обойти две коллекции в определённом порядке. Если бы это была одна сущность, всё было бы заметно сложнее, пришлось бы писать свой класс коллекции. Но благодаря разделению этих интерфейсов, мы можем гибко комбинировать. blog.shamanland.com/2016/03/composite-iterator-ru.html
8 мар 2016 в 14:32
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Можно было бы об этом задуматься, если предполагаемое соответствие Iterable и его Iterator было «один к одному». Но дело обстоит иначе: Iterable может в некий момент обходиться несколькими Iterator сразу (возможно, ещё и в разных thread’ах!). То есть, это интерфейсы, представляющие две разных сущности: коллекцию и обход коллекции.
Уже поэтому стоит их разделить, поскольку в противном случае для нескольких параллельных обходов коллекция должна быть всегда завёрнута в итератор и иметь два вида копирования (для итератора и для коллекции), а тут уже просматривается явное нарушение SRP (Single Responsibility Principle, принципа единственной обязанности).
Отслеживать
ответ дан 8 фев 2016 в 6:37
user181100 user181100
Один — тот кто ездит. Это Iterator
Другой — тот на ком ездят. Это Iterable
Первый не возможен без Второго. Второй возможен без первого, но бесполезен.
А на самом деле Iterable — всего лишь способ задать стандартный механизм извлечения итератора. Он не является таким уж важным в деле итерации. Он не нужен совсем.
Например у List есть ещё listIterator, к которому Iterable вообще никакого отношения не имеет. но тем не менее это настоящий итератор и даже упруженный дополнительными возможностями.
Отслеживать
ответ дан 8 июн 2017 в 0:20
6,288 1 1 золотой знак 12 12 серебряных знаков 18 18 бронзовых знаков
. все это отлично смотрелось бы в одном интерфейсе
Не смотрелось бы.
Iterator — это интерфейс. То есть мы можем вызвать метод какого-то класса, передать ему итератор и как бы сказать: «вот тебе некая последовательность, пробегись-ка по ней и сделай ряд действий».
Благодаря абстрактности итератор может быть вообще не привязан ни к какой коллекции, а самостоятельно генерировать значения на лету!
Причём эти действия, связанные с последовательным проходом по элементам, одинаковы для любого типа контейнера.
Но даже если считать, что любой итератор завязан на своём контейнере, то всё равно получается нехорошо. Итератор — это вспомогательная сущность, состояние которой надо хранить. Плюс, как правильно заметил @D-side, итератор даже уровне восприятия является отдельной сущностью.
Почему в Java интерфейс Iterator не является Iterable?
Интерфейс Iterable представляет собой механизм для создания множества независимых итераторов, в то время как Iterator — это всего лишь единственный проход по элементам. Чтобы использовать Iterator в цикле for-each , его можно обернуть в Iterable следующим образом:
Скопировать код
public class SingleUseIterable implements Iterable < private IteratororiginalIterator; public SingleUseIterable(Iterator iterator) < this.originalIterator = Objects.requireNonNull(iterator, "Iterator не должен быть null для предотвращения NullPointerException"); >@Override public Iterator iterator() < if (originalIterator == null) < throw new IllegalStateException("Упс, данный итератор уже был использован."); >Iterator tempIterator = originalIterator; originalIterator = null; // Теперь итератор может быть использован только однажды. return tempIterator; > > // Пример использования Iterable Iterable iterableWrapper = new SingleUseIterable<>(myIterator); for (String s : iterableWrapper) < // Обрабатываем строку s, используя возможности Iterable >
Теперь давайте более подробно разберемся, в чем заключается разница между Iterator и Iterable , чтобы лучше понять их взаимосвязь.
Независимое состояние и однонаправленность Iterator
Iterator сохраняет информацию о своем текущем состоянии — он помнит о положении последнего элемента в коллекции. Это свойство делает итераторы неотъемлемым инструментом в многопоточной среде. Iterable , напротив, гарантирует создание нового Iterator при каждом вызове метода iterator() , позволяя этим образом нескольким потокам обрабатывать данные независимо, подобно бегунам на различных дорожках стадиона.
Разрешение конфликта между Iterator и Iterable
Если бы интерфейс Iterator был реализован как Iterable , это вызвало бы проблемы из-за необходимости реализации метода iterator() , уменьшив тем самым независимость итераторов. Чтобы обойти это, итератор можно адаптировать к Iterable и использовать в цикле for-each .
Визуализация
Вы можете представить Iterable как плейлист ваших любимых песен , а Iterator — как музыкальный плеер:
Скопировать код
Iterable (): Плейлист, который можно слушать снова и снова. Iterator (▶️): Воспроизведение одной песни.
Iterable можно многократно использовать, как плейлист с функцией повтора. В свою очередь, Iterator напоминает функцию воспроизведения одного трека — каждый проход уникален («Play it again, Sam!» – но только однажды).
Java 8: Большой шаг для итераторов
В версии Java 8 интерфейс Iterator был дополнен методом forEachRemaining() , который дает возможность обрабатывать оставшиеся элементы с использованием лямбда-выражений. Это улучшение открывает возможности для оптимизации обхода данных, существенно повышая производительность коллекций.
Лучшие практики: Использование Iterator и Iterable
Использование обертки Iterable для Iterator стоит рассматривать как особенный случай, например, при работе со старым API или когда доступен только Iterator . В современной Java существуют более удобные варианты: начиная от API потоков и заканчивая усовершенствованным циклом for и методом forEachRemaining() . Благодаря таким новшествам работа с итерациями в Java стала значительно приятнее.
Полезные материалы
- Iterable (Java Platform SE 8 ) — Официальная документация на интерфейс Iterable .
- Iterator (Java Platform SE 8 ) — Детальное описание интерфейса Iterator в документации Oracle.
- Design Patterns – Iterator Pattern — Статья об образце проектирования итератора, которая поможет вам углубить знания в области Java.
- java – Why does Iterable not provide stream() and parallelStream() methods? – Обсуждение возможностей и ограничений Iterable на Stack Overflow.
- Effective Java Item 18 – Рекомендации по использованию интерфейсов в Java основанные на книге «Effective Java».
Итераторы
Итераторы придумали практически тогда, когда и коллекции. Основная задача коллекций была – хранить элементы, а основная задача итератора – выдавать эти элементы по одному.
— А что сложного в том, чтобы выдать набор элементов?
— Во-первых, некоторые коллекции, как например Set не имеют установленного порядка элементов и/или он постоянно меняется.
Во-вторых, некоторые структуры данных могут хранить объекты очень сложно: различными группами, списками и т.д. Т.е. задача отдать последовательно все элементыбудет сложной и нетривиальной.
В третьих – коллекции имеют свойство меняться. Решил ты вывести на экран все содержимое коллекции, а прямо в середине вывода JVM переключилась на другую нить, которая половину элементов из этой коллекции заменила на другую. Вот и получишь ты вместо вывода не пойми что.
— Вот! Именно такие проблемы должен был решить итератор. Итератор – это специальный внутренний объект в коллекции, который с одной стороны имеет доступ ко всем ее private данным и знает ее внутреннюю структуру, с другой – реализует общедоступный интерфейс Iterator, благодаря чему все знают, как с ним работать.
Некоторые итераторы имеют внутри себя массив, куда копируются все элементы коллекции во время создания итератора. Это гарантирует, что последующее изменение коллекции не повлияет на порядок и количество элементов.
Думаю, ты уже сталкивался с тем, что при работе с for each нельзя одновременно «идти по коллекции циклом» и удалять из нее элементы. Это все именно из-за устройства итератора.
В новых коллекциях, добавленных в библиотеке concurrency, устройство итератора переработано, поэтому там такой проблемы нет.
Давай я тебе напомню, как устроен итератор.
В Java есть специальный интерфейс Iterator, вот какие у него методы:
Методы интерфейса Iterator | Описание |
---|---|
boolean hasNext() | Проверяет, есть ли еще элементы |
E next() | Возвращает текущий элемент и переключается на следующий. |
void remove() | Удаляет текущий элемент |
Итератор позволяет поочередно получить все элементы коллекции. Логичнее представить итератор чем-то вроде InputStream – у него есть все данные, но его задача выдавать их последовательно.
Метод next() возвращает следующий (очередной) элемент коллекции.
Метод hasNext() используется, чтобы проверять, есть ли еще элементы.
Ну, а remove() – удаляет текущий элемент.
— А почему методы называются так странно? Почему не isEmpty() или getNextElement()?
Разве так не логичнее?
— Логичнее, но такие названия пришли из языка C++, где итераторы появились раньше.
Кроме итератора есть еще интерфейс Iterable – его должны реализовывать все коллекции, которые поддерживают итератор. У него есть единственный метод:
Методы interface Iterable | Описание |
---|---|
Iteratoriterator() | Возвращает объект-итератор |
С помощью этого метода у любой коллекции можно получить объект итератор для обхода ее элементов. Давай обойдем все элементы дерева в коллекции TreeSet:
TreeSetString> set = new TreeSetString>(); IteratorString> iterator = set.iterator(); while (iterator.hasNext()) < String item = iterator.next(); System.out.println(item); >
Такое использование итератора не очень удобно – слишком много лишнего и очевидного кода. Ситуация упростилась, когда в Java появился цикл по итератору – for-each.
Теперь такой код гораздо компактнее и читабельнее:
TreeSetString> set = new TreeSetString>(); IteratorString> iterator = set.iterator(); while (iterator.hasNext()) < String item = iterator.next(); System.out.println(item); >
TreeSetString> set = new TreeSetString>(); for(String item : set) < System.out.println(item); >
Это один и тот же код! Итератор используется и там, и там.
Просто в цикле for-each его использование скрыто. Обрати внимание – в коде справа вообще нет красного цвета . Использование итератора скрыто полностью.
Цикл for-each можно использовать для любых объектов, которые поддерживают итератор. Т.е. ты можешь написать свой класс, добавить ему метод iterator() и сможешь использовать его объекты в правой части конструкции for-each.
— Ого! Я, конечно, не рвусь писать собственные коллекции и итераторы, но предложение все равно заманчивое. Возьму на карандаш.
— Кроме того, есть еще одна популярная разновидность итераторов, для которой даже придумали свой интерфейс. Речь идет об итераторе для списков – ListIterator.
Списки, независимо от реализации, обладают порядком элементов, что в свою очередь позволяет работать с ними через итератор чуть более удобно.
Вот какие методы есть у интерфейса ListIterator:
Метод | Описание |
---|---|
boolean hasNext() | Проверяет, есть ли еще элементы впереди. |
E next() | Возвращает следующий элемент. |
int nextIndex() | Возвращает индекс следующего элемента |
void set(E e) | Меняет значение текущего элемента |
boolean hasPrevious() | Проверяет, есть ли элементы позади. |
E previous() | Возвращает предыдущий элемент |
int previousIndex() | Возвращает индекс предыдущего элемента |
void remove() | Удаляет текущий элемент |
void add(E e) | Добавляет элемент в список. |
Т.е. тут мы можем ходить не только вперед, но и назад. И еще пара фич по мелочи.
— Что ж, интересная штука. А где его используют?
— Например, ты хочешь двигаться туда-обратно по связному списку. При этом операция get будет довольно медленной, а операция next() очень быстрой.
— Хм. Убедила. Буду иметь ввиду.
Iterable. Сравнение с Iterator
Iterable представляет собой простое представление ряда однородных элементов, которые могут повторяться определенное количество раз. Он не имеет никакого итерационного состояния, такого как «текущий элемент». Вместо этого он имеет один метод, который создает Iterator .
Iterator — объект с итерационным состоянием. Он позволяет вам проверить, есть ли у него больше элементов с помощью метода hasNext() и перейти к следующему элементу (если он есть) с помощью метода next() .
Как правило, Iterable должен иметь возможность выдавать любое количество допустимых Iterator-ов.
Реализация Iterable — это та, которая сама предоставляет Iterator :
public interface IterableT> < Iterator iterator(); >
Итератор — это простой способ разрешить некоторым циклическим путем собирать данные без возможности изменения элементов коллекции (хотя и с возможностью их удаления).
public interface IteratorE> < boolean hasNext(); E next(); void remove(); >
Можно рассмотреть эти 2 интерфейса еще с одной стороны:
- Итерируемый (Iterable): Класс, который можно повторить. То есть тот, у которого есть понятие «принесите мне первое, теперь следующее, и так далее, пока мы не закончим».
- Итератор (Iterable): Класс, который управляет итерацией по итерируемому. То есть, он отслеживает, где мы находимся в текущей итерации, и знает, что представляет собой следующий элемент и как его получить.
Чтобы сделать объект итерабельным, он должен возвращать объект Iterator . Чтобы обеспечить выполнение этого контракта, необходимо использовать интерфейс Iterable . Он содержит метод с именем iterator() и возвращает Iterator . Следовательно, любой класс, реализующий Iterable , может вернуть Iterator .
Пример
public interface CollectionE> extends IterableE> <>
Коллекция представляет собой интерфейс, представляющий контейнер для ряда элементов, и все коллекции, такие как ArrayList , LinkedList и т.д. реализуют интерфейсы как Collection , так и Iterable .