Как сделать итератор java

Добавил пользователь Дмитрий К.
Обновлено: 04.10.2024

Есть еще одно средство, предоставляемое Java, которое позволяет писать компактный код – это итератор. Именно об этом мы и поговорим в данном материале.

Итак, мы хотим позволить клиенту перебирать элементы в коллекции. Но у нас нет клиента, чтобы знать, используем ли мы массив или список ссылок или какое-либо внутреннее представление, которое мы могли бы иметь в виду. Это не имеет отношения к клиенту. И многие клиенты только хотят просто перебирать вещи в коллекции. И Java предоставляет хорошее решение для этой так называемой итерации.

Мы собираемся посмотреть, как сделать наш стек, очередь и другие структуры данных реализуют так называемый Iterable интерфейс, и он будет работать для клиентского кода независимо от того, какую реализацию мы использовали, поэтому давайте рассмотрим детали этого. Так что же такое итерация?

Ну, в Java lingo, Iterable, это класс, который имеет метод, который возвращает итератор. Так что же такое итератор? Ну итератор-это что-то, класс, который имеет методы hasNext() и Next(). Java также позволяет удалить (). Мы думаем, что одна плохая новость, мы не используем его может привести к коварной проблеме отладки ошибок. Итак, это hasNext() и next() и поэтому, чтобы сделать структуру данных итеративной, мы собираемся реализовать эти вещи.

Кажется, что у нас много багажа, и причина, по которой мы это делаем, почему мы идем на это, заключается в том, что мы можем, если у нас есть структура данных, которая повторяется, мы можем использовать очень компактный и элегантный клиентский код на Java, так называемый оператор for-each. Поэтому, если у нас есть стек, мы можем сказать - (для строки s : stack). Это означает для каждой строки в стеке - распечатать его.

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

Вот как это выглядит для списка ссылок.

Итак, он должен реализовать Iterable так что же это значит реализовать Iterable? Он должен иметь метод iterator (), который возвращает итератор. Так что же такое итератор?

Мы собираемся использовать внутренний класс. В этом случае мы будем называть его ListIterator, который реализует Iterator и является универсальным. И в основном то, что эта вещь должна сделать, это реализовать эти методы hasNext() и next(). А семантика просто понятна из названий. hasNext () должен if, if we'Re done должен возвращать false.

Если мы не закончили, мы должны вернуть true, а next() должен дать следующий элемент в итерации. Поэтому, если дело в связанном списке, мы начнем сначала. У нас есть это, наш первый элемент в списке, и мы собираемся поддерживать переменную экземпляра current внутри этого итератора, который является текущей вещью, которую мы повторяем. Итак, получите следующий, как если бы мы хотели удалить первый.

Мы вытаскиваем текущий элемент, а затем продвигаем текущую ссылку и возвращаем элемент. Перемещение тока на следующее место. Клиент всегда будет тестировать hasNext (), как я показал, как я показал, и что заглушка код до и поэтому, когда он получает null он будет возвращать false в итерационной остановки. Но для нашей итерации нам просто нужно беспокоиться о реализации next() и hasNext() и, возможно, использовать локальную переменную экземпляра, чтобы сделать это.

Мы должны, вероятно, сделать пуленепробиваемые исключения кода, если клиент пытается вызвать next() без каких - либо элементов и пытается вызвать remove() вообще, мы не будем поддерживать remove(). Для, и для массива, это еще проще. Итак, теперь с итератором у нас есть контроль над тем, в каком порядке мы проходим через элементы, и поэтому это будет идти вместе с семантикой и структурой данных, поэтому, вероятно, в стеке вы хотите получить вещи в порядке стека, например, порядок, который выходит из стека, так что это обратный порядок в массиве, поэтому в этом случае next() просто декремент и возвращает следующий, а наша переменная экземпляра является индексом в массиве.

И тогда hasNext () в порядке, пока эта вещь положительна. Итак, немного кода Java чтобы обеспечить эту итерацию, но на самом деле в этой структуре не слишком много, и вы можете увидеть, как реализовать это для своего собственного типа данных, и мы будем использовать эту парадигму для каждого базового типа данных, который мы, который включает в себя коллекции объектов, с которыми мы столкнемся.

Хорошо, и на самом деле, это приводит нас к тому, что на самом деле для многих клиентов не имеет значения, какой заказ мы получаем. Очень часто мы просто вставляем элементы в коллекцию, а затем, позже, повторяем элементы, которые у нас есть. Эта структура данных называется сумкой, поэтому давайте посмотрим, как выглядит этот API.

Заказ не имеет значения, поэтому все, что мы хотим сделать, это добавить элемент, возможно, вы хотите знать размер, и мы хотим, чтобы перебрать все элементы в сумке. Таким образом, это более простой, более узкий API, но все же он выражает важную небольшую коллекцию операций, и мы будем использовать этот, и мы уже видели реализации. Вы просто берете стек и удаляете pop, или очередь и удаляете dequeue и у вас есть прекрасная реализация полезной структуры данных.


Итератор (от англ. iterator ― перечислитель) — интерфейс, предоставляющий доступ к элементам коллекции (массива или контейнера) и навигацию по ним. В различных системах итераторы могут иметь разные общепринятые названия. В терминах систем управления базами данных итераторы называются курсорами.

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

Ну и судя по моим наблюдениям Хабр — ума палата, буду рад любым комментариям и предложениям, которые помогут протестировать или улучшить предлагаемое решение.

Стандартный итератор

В обычных языках программирования итератор реализуется довольно просто. Выбирается одно или несколько ключевых слов для операции перебора элементов итератора и описывается необходимый для его реализации интерфейс.

Например в С++ это делается с помощью конструкций вида:
Или так:
В Python так же любой пользовательский класс тоже может поддерживать итерацию. Для этого нужно определить метод __iter__(), который и создает итератор и метод next(), который должен возвращать следующий элемент.


В Java использование итераторов выглядит примерно также.

Общепринятая логика у итератора следующая:

Концепция итератора для нового языка

Как и любому языку программирования, в новом языке так же требуется обеспечить способ навигации по коллекциям объектов (элементам списков/массивов). Поэтому при проектировании синтаксиса мне тоже пришлось задуматься над реализацией данного функционала. Причем, основная сложность возникла не с реализацией, а с записью самой операции при работе с итератором в исходном коде из-за специфических требований к синтаксису языка, а именно из-за отсутствия в языке зарезервированных ключевых слов.

Более того, при разработке языка программирования, мне хотелось унифицировать доступ не только к локальным свойствам объектов или элементам коллекций, но и использовать этот же механизм для доступа к внешним данным, включая записи в реляционных БД.

В большей степени из-за необходимости работать с объектами из базы данных, так же как с локальными объектами, итоговая концепция итератора немного (или наоборот много) отличается от общепринятой классической, вынесенной в заголовок статьи.

Создание итератора

В случае реализации доступа к локальным данным внутри приложения, нет никаких причин разделять создание итератора и получение указателя на его первый элемент. Особенно в компилируемых языках (например С++), в которых итератор может быть реализован обычной ссылкой на элемент данных. В таких случаях, как правило, проблем при создании итератора не предвидится.

Однако, в случае создания итератора на основе, например файла, уже могу возникнуть ошибки. Причем как во время создания итератора (файл отсутствует или недостаточно прав доступа), так и во время получения следующего элемента данных (например, при ошибке чтения файла). Естественно, данные ситуации приходится обрабатывать на уровне исходного кода программы, либо внутри объекта, либо уже при использовании итератора, например ловя возникающие исключения.

Но в случае с базами данных ситуация становится еще интереснее. Кроме обработки возможных ошибок, требуется еще предусмотреть возможность указания различных параметров при создании итератора. Например, с учетом фильтрации данных, их сортировки или для оптимизации скорости выполнения запроса. Конечно и при файловых операциях это тоже может потребоваться, но при создании итератора тут мало что можно сделать. А вот в случае работы с курсором базы данных, можно предусмотреть создание итератора с учетом имеющихся индексов или внешних связей у таблиц.

Автоматическое перемещение итератора*

Как было указано выше, перемещение внутреннего указателя на следующий элемент данных выполняется всегда и автоматически, а если необходимо оставлять курсор на текущей позиции (т.е. считывать одни и те же данные постоянно), то после каждого чтения необходимо возвращать позицию курсора назад, на количество реально считанных данных. Фактически это повторяет логику работы итератора в Python (т.е. выполнение метода next).

Такое поведение было выбрано из-за желания сделать работу с итератором как можно более лаконичной. Ведь тогда в подавляющем большинстве случаев для его использования можно будет обойтись всего одной операцией — получением данных, и при её успешном выполнении, внутренний указатель будет перемещаться на следующую порцию данных без необходимости выполнять данную операцию явно.

Завершение работы итератора*

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

Другими словами, если использовать такое поведение итератора, то при получении текущего значения становится неважно, к каким данным происходит обращение и использовать интерфейс итератора можно даже при обращении к обычным переменным (не коллекциям)!

Просто в этом случае всегда будет возвращаться её актуальное значение. Конечно, последующее перемещение курсора на очередной элемент данных не произойдет, но сами данные с точки зрения логики работы итератора будут корректны.

Синтаксис лаконичного итератора

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

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

Довольно часто в различных языках программирования встречается управляющая конструкция вида name? или ?name. При этом интуитивно понятно, что требуется вывести значение переменой в консоль или отладочный лог. Причем, данная конструкция выглядит очень естественно и согласуется с привычным использованием данного знака препинания (знак вопроса).

И приняв это за отправную точку, операция чтения данных из итератора в декларативном стиле получается следующая:

? — итератор возвращает весь набор элементов из коллекции в виде массива. Коллекция возвращается полностью, независимо от текущего положения курсора. Фактически, это означает, что перед каждым выполнением данного оператора, у итератора выполняется операция сброса в начальное состояние. Так же, с помощью конструкции вида ?(. ) можно считывать произвольное количество возвращаемых элементов, например для пагинации.

! — итератор возвращает один текущий объект из коллекции и переводит указатель на следующий элемент (сброс/инициализация итератора выполняется неявно и автоматически при обращении к первого элементу). В данном случае, операция чтения итератора возвращает именно сам элемент, а не массив с одним элементом. Если необходимо считать элемент данных как элемент массива, то необходимо использовать конструкцию ?(1).

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

Хотя при необходимости, количество операторов для работы с итераторами может быть расширено. Например можно добавить оператор "!-" или "!!", который будет считывать один элемент данных и возвращать указатель на одну позицию назад, а оператор "??" считывать только остаток коллекции без принудительного сброса указателя в начало перед чтением данных.

Примеры использования

Мне кажется, что синтаксис получения данных с помощью итератора получился понятным, несмотря на его лаконичность, за счет использования близких по смыслу или по написанию знаков препинания.

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

*) Некоторые особенности работы итератора оказались не слишком удачными в использовании, поэтому окончательно поведение итератора немного отличается от описанного в данной статье.

Сегодняшний шаблон — это шаблон Iterator, который формализует то, как мы перемещаемся по коллекции данных в определенном классе.

Итератор в реальном мире

Управление MP3-плеером является хорошим примером итератора. Пользователь не возражает против того, как просмотреть свой список песен, как только он их увидит. В старых mp3-плеерах это делалось с помощью простых кнопок вперед и назад. С iPod это изменилось на концепцию колесной навигации. IPhone перемещает это дальше, чтобы использовать движения смахивания. Тем не менее, все интерфейсы поддерживают одну и ту же идею — способ перебора вашей музыкальной коллекции.

Refcard Designs Patterns
Для лучшего обзора самых популярных шаблонов дизайна, лучше всего начать с Refcard Designs Patterns от DZone .

Шаблон итератора

Предоставляет способ доступа к элементам агрегатного объекта без раскрытия его базового представления.

Давайте посмотрим на определение диаграммы, прежде чем углубляться в детали.

Совокупный определяет интерфейс для создания объекта итератора. В ConcreteAggregate реализует этот интерфейс, и возвращает экземпляр ConcreteIterator. Итератора определяет интерфейс для доступа и обхода элементов, а ConcreteIterator реализует этот интерфейс , сохраняя при текущей позиции в обход агрегата.

Используя этот шаблон, вы можете использовать стандартную концепцию итерации для определения специальных итераторов, которые возвращают только определенные элементы в наборе данных.

Буду ли я использовать этот шаблон?

Этот шаблон полезен, когда вам нужен доступ к элементам набора без доступа ко всему представлению. Когда вам нужен унифицированный интерфейс обхода, и несколько элементов могут проходить через элементы, итератор — хороший выбор.

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

В Java итераторы очень активно используются в Collection Framework. Для этих целей создан специальный interface с одноименным названием java.util.Iterator. Интерфейс содержит следующие методы:

  • hasNext() - возвращает буелове значение в зависимости от того, есть ли еще элементы в коллекции.
  • next() - возвращает следующий элемент в коллекции. В соответствии с API метод должен бросать NoSuchElementException если все элементы уже пройдены.
  • remove() - удаляет элемент, который был возвращен последним вызовом next. К этому методу есть некоторые вопросы :) - почему собственно он находится в интерфейсе, ведь не каждая коллекция позволяет удалять элементы, в конце концов есть read only коллекции. Именно поэтому начиная с Java 1.8 метод remove описан как default с такой реализацией по умолчанию: default void remove()

Давайте опишем некоторый класс и реализуем для него Iterator.

Предположим у нас есть ужасный монстр, который состоит из головы, руки и ноги. Для каждой части есть сеттер, геттер, а так же для удобства метод, который говорит нам о наличии либо отсутствии части.

Класс итератора можно разместить в том же файле. Пусть индекс (index) отвечает за так называемый курсор в итераторе. Значение -1 будет установлено при создании нового объекта. Значения 0, 1 и 2 будут соответствовать голове, руке и ноге.

Реализуем методы hasNext, next и remove. Кстати, remove можно не реализовывать начиная с версии Java 1.8. В этом случае будет использована реализация по-умолчанию (смотрите начало статьи).

Реализация итератора получилась не очень красивая. Просто я хотел показать, что итерировать можно не только по коллекции, а по любому составному объекту.

Для того, чтобы можно было удобнее создавать экземпляр итератора был введен интерфейс Iterable с методом iterator(). Можно описать класс Monster как реализующий этот интерфейс и реализовать метод iterator таким образом:

Читайте также: