Итераторы
Итераторы придумали практически тогда, когда и коллекции. Основная задача коллекций была – хранить элементы, а основная задача итератора – выдавать эти элементы по одному.
— А что сложного в том, чтобы выдать набор элементов?
— Во-первых, некоторые коллекции, как например Set не имеют установленного порядка элементов и/или он постоянно меняется.
Во-вторых, некоторые структуры данных могут хранить объекты очень сложно: различными группами, списками и т.д. Т.е. задача отдать последовательно все элементыбудет сложной и нетривиальной.
В третьих – коллекции имеют свойство меняться. Решил ты вывести на экран все содержимое коллекции, а прямо в середине вывода JVM переключилась на другую нить, которая половину элементов из этой коллекции заменила на другую. Вот и получишь ты вместо вывода не пойми что.
— Вот! Именно такие проблемы должен был решить итератор. Итератор – это специальный внутренний объект в коллекции, который с одной стороны имеет доступ ко всем ее private данным и знает ее внутреннюю структуру, с другой – реализует общедоступный интерфейс Iterator, благодаря чему все знают, как с ним работать.
Некоторые итераторы имеют внутри себя массив, куда копируются все элементы коллекции во время создания итератора. Это гарантирует, что последующее изменение коллекции не повлияет на порядок и количество элементов.
Думаю, ты уже сталкивался с тем, что при работе с for each нельзя одновременно «идти по коллекции циклом» и удалять из нее элементы. Это все именно из-за устройства итератора.
В новых коллекциях, добавленных в библиотеке concurrency, устройство итератора переработано, поэтому там такой проблемы нет.
Давай я тебе напомню, как устроен итератор.
В Java есть специальный интерфейс Iterator, вот какие у него методы:
Методы интерфейса Iterator | Описание |
---|---|
boolean hasNext() | Проверяет, есть ли еще элементы |
E next() | Возвращает текущий элемент и переключается на следующий. |
void remove() | Удаляет текущий элемент |
Итератор позволяет поочередно получить все элементы коллекции. Логичнее представить итератор чем-то вроде InputStream – у него есть все данные, но его задача выдавать их последовательно.
Метод next() возвращает следующий (очередной) элемент коллекции.
Метод hasNext() используется, чтобы проверять, есть ли еще элементы.
Ну, а remove() – удаляет текущий элемент.
— А почему методы называются так странно? Почему не isEmpty() или getNextElement()?
— Логичнее, но такие названия пришли из языка C++, где итераторы появились раньше.
Кроме итератора есть еще интерфейс Iterable – его должны реализовывать все коллекции, которые поддерживают итератор. У него есть единственный метод:
С помощью этого метода у любой коллекции можно получить объект итератор для обхода ее элементов. Давай обойдем все элементы дерева в коллекции 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() очень быстрой.
— Хм. Убедила. Буду иметь ввиду.
To use iterator in java
Performs the given action for each remaining element until all elements have been processed or the action throws an exception.
Removes from the underlying collection the last element returned by this iterator (optional operation).
Method Detail
hasNext
Returns true if the iteration has more elements. (In other words, returns true if next() would return an element rather than throwing an exception.)
next
remove
Removes from the underlying collection the last element returned by this iterator (optional operation). This method can be called only once per call to next() . The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.
forEachRemaining
Performs the given action for each remaining element until all elements have been processed or the action throws an exception. Actions are performed in the order of iteration, if that order is specified. Exceptions thrown by the action are relayed to the caller.
while (hasNext()) action.accept(next());
Submit a bug or feature
For further API reference and developer documentation, see Java SE Documentation. That documentation contains more detailed, developer-targeted descriptions, with conceptual overviews, definitions of terms, workarounds, and working code examples.
Copyright © 1993, 2023, Oracle and/or its affiliates. All rights reserved. Use is subject to license terms. Also see the documentation redistribution policy.
Java Iterator
An Iterator is an object that can be used to loop through collections, like ArrayList and HashSet. It is called an «iterator» because «iterating» is the technical term for looping.
To use an Iterator, you must import it from the java.util package.
Getting an Iterator
The iterator() method can be used to get an Iterator for any collection:
Example
// Import the ArrayList class and the Iterator class import java.util.ArrayList; import java.util.Iterator; public class Main < public static void main(String[] args) < // Make a collection ArrayListcars = new ArrayList(); cars.add("Volvo"); cars.add("BMW"); cars.add("Ford"); cars.add("Mazda"); // Get the iterator Iterator it = cars.iterator(); // Print the first item System.out.println(it.next()); > >
Looping Through a Collection
To loop through a collection, use the hasNext() and next() methods of the Iterator :
Example
Removing Items from a Collection
Iterators are designed to easily change the collections that they loop through. The remove() method can remove items from a collection while looping.
Example
Use an iterator to remove numbers less than 10 from a collection:
import java.util.ArrayList; import java.util.Iterator; public class Main < public static void main(String[] args) < ArrayListnumbers = new ArrayList(); numbers.add(12); numbers.add(8); numbers.add(2); numbers.add(23); Iterator it = numbers.iterator(); while(it.hasNext()) < Integer i = it.next(); if(i < 10) < it.remove(); >> System.out.println(numbers); > >
Note: Trying to remove items using a for loop or a for-each loop would not work correctly because the collection is changing size at the same time that the code is trying to loop.
Паттерн Iterator
Как ты уже, вероятно, знаешь, в Java есть замечательный интерфейс Collection, реализующий интерфейс Iterator. Сразу оговорюсь, не следует путать интерфейс iterator с паттерном iterator в Java! И дабы внести ясность, для начала разберёмся именно с интерфейсом.
Дословно «Iterator» можно перевести как «переборщик». То есть это некая сущность, способная перебрать все элементы в коллекции. При этом она позволяет это сделать без вникания во внутреннюю структуру и устройство коллекций. |
Представим на секунду, что iterator в Java отсутствует. В таком случае всем и каждому придётся нырнуть в самые глубины коллекций и по-настоящему разобраться, чем отличается, ArrayList от LinkedList и HashSet от TreeSet .
Методы, которые должен имплементировать Iterator
- UnsupportedOperationException , если данный итератор не поддерживает метод remove() (в случае с read-only коллекциями, например)
- IllegalStateException , если метод next() еще не был вызван, или если remove() уже был вызван после последнего вызова next() .
- void add(E e) — вставляет элемент E в List ;
- boolean hasPrevious() — вернет true , если при обратном переборе List имеются элементы;
- int nextIndex() — вернет индекс следующего элемента;
- E previous() — вернет предыдущий элемент листа;
- int previousIndex() — вернет индекс предыдущего элемента;
- void set(E e) — заменит элемент, возвращенный последним вызовом next() или previous() на элемент e .
List list = new ArrayList<>(); list.add("Привет"); list.add("Обучающимся"); list.add("На"); list.add("JavaRush");
Iterator iterator = list.iterator(); while (iterator.hasNext())
Сейчас будет «узкое место»: Java Collections как ты, вероятно, знаешь (а если не знаешь, разберись), расширяют интерфейс Iterable , но это не означает, что только List , Set и Queue поддерживают итератор. Для java Map iterator также поддерживается, но его необходимо вызывать для Map.entrySet() :
Map map = new HashMap<>(); Iterator mapIterator = map.entrySet().iterator();
Тогда метод next() будет возвращать объект Entry , содержащий в себе пару «ключ»-«значение». Дальше все аналогично с List :
while (mapIterator.hasNext()) < Map.Entryentry = mapIterator.next(); System.out.println("Key: " + entry.getKey()); System.out.println("Value: " + entry.getValue()); >
Ты думаешь: «Стоп. Мы говорим про интерфейс, а в заголовке статьи написано «Паттерн». То есть, паттерн iterator – это интерфейс Iterator? Или интерфейс — это паттерн?» Если это слово встречается впервые, даю справку: паттерн — это шаблон проектирования, некое поведение, которого должен придерживаться класс или множество взаимосвязанных классов. Итератор в java может быть реализован для любого объекта, внутренняя структура которого подразумевает перебор, при этом можно изменить сигнатуру обсуждаемых методов. Главное при реализации паттерна – логика, которой должен придерживаться класс. Интерфейс итератор – частная реализация одноименного паттерна, применяемая как к готовым структурам ( List, Set, Queue, Map ), так и к прочим, на усмотрение программиста. Расширяя интерфейс Iterator, ты реализуешь паттерн, но для реализации паттерна не обязательно расширять интерфейс. Простая аналогия: все рыбы плавают, но не всё, что плавает – рыбы. В качестве примера я решил взять… слово. А конкретнее — существительное. Оно состоит из частей: приставки, корня, суффикса и окончания. Для частей слова создадим интерфейс WordPart и классы, расширяющие его: Prefix, Root, Suffix и Ending :
interface WordPart < String getWordPart(); >static class Root implements WordPart < private String part; public Root(String part) < this.part = part; >@Override public String getWordPart() < return part; >> static class Prefix implements WordPart < private String part; public Prefix(String part) < this.part = part; >@Override public String getWordPart() < return part; >> static class Suffix implements WordPart < private String part; public Suffix(String part) < this.part = part; >@Override public String getWordPart() < return part; >> static class Ending implements WordPart < private String part; public Ending(String part) < this.part = part; >@Override public String getWordPart() < return part; >>
Тогда класс Word (слово) будет содержать в себе части, а кроме них добавим целое число, отражающее количество частей в слове:
public class Word < private Root root; private Prefix prefix; private Suffix suffix; private Ending ending; private int partCount; public Word(Root root, Prefix prefix, Suffix suffix, Ending ending) < this.root = root; this.prefix = prefix; this.suffix = suffix; this.ending = ending; this.partCount = 4; >public Word(Root root, Prefix prefix, Suffix suffix) < this.root = root; this.prefix = prefix; this.suffix = suffix; this.partCount = 3; >public Word(Root root, Prefix prefix) < this.root = root; this.prefix = prefix; this.partCount = 2; >public Word(Root root) < this.root = root; this.partCount = 1; >public Root getRoot() < return root; >public Prefix getPrefix() < return prefix; >public Suffix getSuffix() < return suffix; >public Ending getEnding() < return ending; >public int getPartCount() < return partCount; >public boolean hasRoot() < return this.root != null; >public boolean hasPrefix() < return this.prefix != null; >public boolean hasSuffix() < return this.suffix != null; >public boolean hasEnding()
Окей, у нас есть четыре перегруженных конструктора (для простоты, предположим, что суффикс у нас может быть только один). Существительное не может состоять из одной приставки, поэтому для конструктора с одним параметром будем устанавливать корень. Теперь напишем реализацию паттерна итератор: WordIterator, переопределяющий 2 метода: hasNext() и next() :
public class WordIterator implements Iterator < private Word word; private int wordPartsCount; public WordIterator(Word word) < this.word = word; this.wordPartsCount = word.getPartCount(); >@Override public boolean hasNext() < if (wordPartsCount == 4) < return word.hasPrefix() || word.hasRoot() || word.hasSuffix() || word.hasEnding(); >else if (wordPartsCount == 3) < return word.hasPrefix() || word.hasRoot() || word.hasSuffix(); >else if (wordPartsCount == 2) < return word.hasPrefix() || word.hasRoot(); >else if (wordPartsCount == 1) < return word.hasRoot(); >return false; > @Override public Word.WordPart next() throws NoSuchElementException < if (wordPartsCount try < if (wordPartsCount == 4) < return word.getEnding(); >if (wordPartsCount == 3) < return word.getSuffix(); >if (wordPartsCount == 2) < return word.getPrefix(); >return word.getRoot(); > finally < wordPartsCount--; >> >
public class Word implements Iterable < … @Override public Iteratoriterator() < return new WordIterator(this); >… >