Интерфейс Iterator в Java
В сегодняшнем нашем уроке мы продолжаем такую большую и интересную тему как Collection Framework и темой нашего сегодняшнего урока будут итераторы. Итак, что мы сегодня рассмотрим?
- Варианты перебора содержимого коллекции
- Интерфейс Iterator
- Методы интерфейса Iterator
- Пример использования методов hasNext() и next()
- Пример использования метода forEachRemaining()
- Возникновение ConcurrentModificationException
- Перебор коллекции с помощью цикла for each и интерфейс Iterable
- Интерфейс ListIterator
- Методы интерфейса ListIterator
- Пример использования интерфейса ListIterator
- Выводы
1. Варианты перебора содержимого коллекции
Перебор содержимого коллекции может быть осуществлён двумя способами:
- с помощью итератора
- с помощью цикла for each.
Я думаю, что второй вариант с помощью цикла for each вы уже неоднократно видели. И сегодня мы рассмотрим более детально как это происходит.
2. Интерфейс Iterator
Итак, давайте начнём с интерфейса Iterator. Интерфейс Iterator позволяет осуществлять обход коллекции, предоставляя при этом доступ к элементам коллекции и позволяя параллельно удалять избранные элементы. Интерфейс Iterator является обобщённым, как и все остальные интерфейсы и классы Collection Framework. Для получения итератора, Вы можете использовать метод Iterator<E> iterator(), который находится в интерфейсе Collection.
3. Методы интерфейса Iterator
Давайте рассмотрим методы интерфейса Iterator. Сначала их все перечислим, а потом мы рассмотрим примеры их использования.
boolean hasNext()
- возвращаетtrue
, если есть еще элементы. В противном случае возвращаетfalse
.E next()
- возвращает следующий элемент. Если следующий элемент коллекции отсутствует, то методnext()
генерирует исключениеNoSuchElementException
.void remove()
- удаляет текущий элемент. Возбуждает исключениеIllegalStateException
, если предпринимается попытка вызватьremove()
, которой не предшествовал вызовnext()
.default void forEachRemaining(Consumer<? super E> action)
- выполняет заданное действие для всех оставшихся элементов коллекции.
4. Пример использования методов hasNext() и next()
Давайте начнём с примера использования методов hasNext()
и next()
.
В следующей программе мы рассмотрим как с помощью этих методов перебирать элементы коллекции и просто выводить их на консоль.
Сначала создается список с обобщённым типом String
и добавляются туда строки C A E B D F. В следующей строчке мы получаем итератор с помощью метода iterator()
и записываем его в переменную. Обратите внимание, что итератор является тоже обобщённым и обобщённый тип такой же, как и у первоначальной нашей коллекции arrayList
. Мы можем рассматривать итератор, как курсор, который при получении указывает на нулевой элемент нашей коллекции. И далее мы перемещаем этот курсор по нашей коллекции с помощью метода next()
.
В цикле while
мы сначала вызываем метод hasNext()
. То есть проверяем, а существует ли следующий элемент. И если он есть, только тогда мы вызываем метод next()
. Метод next()
возвращает нам какой-то элемент, который мы записываем в переменную element
, и дальше выводим его на консоль. Метод next()
не только возвращает следующий элемент, но ещё и перемещает курсор нашего итератора в следующую позицию.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorExample {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("C");
arrayList.add("A");
arrayList.add("E");
arrayList.add("B");
arrayList.add("D");
arrayList.add("F");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.print(element + " ");
}
}
}
Результат выполнения нашей программы:
C A E D F
Все классы в каркасе коллекций усовершенствованы таким образом, чтобы реализовывать интерфейс Iterable
. Это означает, что содержимое коллекции можно перебрать, организовав цикл for
в стиле for each
. Конструкция for each
скрывает итератор, поэтому нельзя вызвать метод remove()
.
5. Пример использования метода forEachRemaining()
Следующий метод, который мы с Вами рассмотрим - это метод forEachRemaining
. Этот метод может выполнять заданное действие для всех оставшихся элементов коллекции.
Следующий пример иллюстрирует использование метода forEachRemaining().
В нем мы выводим на консоль все элементы коллекции, добавляя к каждому элементу плюсик. Вначале создается коллекция с обобщённым типом String
, в нее добавляются строки A B C D. Далее получаем итератор с помощью метода iterator()
и в метод forEachRemaining()
передается Consumer. В этом Consumer
мы указываем необходимое действие - просто вывести на консоль каждый элемент, добавляя к нему плюсик.
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("A");
arrayList.add("B");
arrayList.add("C");
arrayList.add("D");
Iterator<String> iterator = arrayList.iterator();
iterator.forEachRemaining(str -> System.out.print(str + "+"));
}
И вот результат выполнения данной программы:
A+B+C+D+
6. Возникновение ConcurrentModificationException
Следующий момент, который мы рассмотрим в этом уроке - это возникновение ConcurrentModificationException
.
Если вы используете итератор для обхода коллекции, то после его получения, вы не можете модифицировать коллекцию. Давайте посмотрим, как это сделано в следующем примере:
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("C");
arrayList.add("A");
arrayList.add("E");
Iterator<String> iterator = arrayList.iterator();
arrayList.removeFirst();
while (iterator.hasNext()) {
System.out.println(iterator.next()); //ConcurrentModificationException
}
}
В этой программе создается список arrayList
, туда добавляются три элемента C A E, и потом получаем итератор. И после получения итератора, удаляем первый элемент. Далее в этой программе при вызове метода next()
, выброситься ConcurrentModificationException
.
Почему так происходит? При получении итератора запоминается состояние коллекции, курсор указывает на какой-то элемент. И если после этого изменяется состояние коллекции, то может возникнуть не совсем очевидная ситуация. Именно поэтому, при попытке переместить курсор на следующий элемент, у нас и возникнет это исключение.
Поэтому при переборе коллекции с помощью итератора, модифицируйте коллекцию ТОЛЬКО с помощью методов итератора. В нашем случае это метод remove()
итератора. Иначе, вы рискуете получить исключение ConcurrentModificationException
.
7. Перебор коллекции с помощью цикла for each и интерфейс Iterable
Следующий пункт нашего урока - это перебор коллекции с помощью цикла for each
. Я думаю, что вы неоднократно это видели, но давайте посмотрим всё равно на пример использования этого цикла:
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("C");
arrayList.add("A");
arrayList.add("E");
arrayList.add("B");
arrayList.add("D");
arrayList.add("F");
for (String element : arrayList) {
System.out.print(element + " ");
}
}
В этой простой программе я создаю arrayList
с обобщённым типом String
, добавляю туда элементы C A E B D F, и потом в цикле for each
просто перебираю элементы и вывожу их на консоль.
Для того чтобы у вас была возможность перебирать элементы какого-то класса с помощью цикла for each
, класс должен реализовать интерфейс Iterable
. То есть, вы не можете перебирать элементы произвольного класса. Если Вы посмотрите на структуру коллекции Collection
, то увидите, что интерфейс Collection
расширяет интерфейс Iterable
. Именно поэтому мы можем перебирать в цикле for each
практически все классы Collection Framework.
И что нужно ещё сказать - конструкция for each
скрывает под капотом итератор, который мы рассмотрели в начале урока. Конечно же, перебирать элементы с помощью цикла for each
проще чем использовать итератор. Зачем нам тогда явно создавать объект итератора? В принципе это необходимо только в той ситуации, когда при переборе коллекции есть необходимость, например, удалять элементы. В этом случае используйте итератор.
8. Интерфейс ListIterator
Следующий интерфейс, который мы рассмотрим в этом уроке - это интерфейс ListIterator
. Этот интерфейс расширяет Iterator
и добавляет свои методы. Как Вы уже, наверное, поняли из названия, ListIterator
используется со списками. То есть с классами, которые реализуют интерфейс List, например, ArrayList
и LinkList
. ListIterator
используется для двустороннего обхода списка из видоизменения его элементов. Двустороннего - это значит, что мы можем идти не только с начала в конец, как это было с интерфейсом Iterator
, но также мы можем перебирать элементы с конца в начало. ListIterator
мы можем получить, вызывая метод listIterator()
для коллекций, реализующих интерфейс List
.
9. Методы интерфейса ListIterator
И давайте перейдём к методам интерфейса ListIterator
:
void add(Е obj)
- вставляетobj
перед элементом, который должен быть возвращен следующим вызовомnext()
.boolean hasNext()
- возвращаетtrue
, если есть следующий элемент. В противном случае возвращаетfalse
.boolean hasPrevious()
- возвращаетtrue
, если есть предыдущий элемент. В противном случае возвращаетfalse
.Е next()
- возвращает следующий элемент. Если следующего нет, инициируется исключениеNoSuchElementException
.int nextIndex()
- возвращает индекс следующего элемента. Если следующего нет, возвращается размер списка.- Е previous() - возвращает предыдущий элемент. Если предыдущего нет, инициируется исключение
NoSuchElementException
. int previousIndex()
- возвращает индекс предыдущего элемента. Если предыдущего нет, возвращается -1.void remove()
- удаляет текущий элемент из списка. Еслиremove()
вызван доnext()
илиprevious()
, инициируется исключениеIllegalStateException
.void set(Е obj)
- присваиваетobj
текущему элементу. Это элемент, возвращенный последним вызовомnext()
илиprevious()
.
10. Пример использования интерфейса ListIterator
Давайте посмотрим на пример использования интерфейса ListIterator
:
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorExample {
public static void main(String[] args) {
List<String> arrayList = Arrays.asList("A", "B", "C", "D");
ListIterator<String> listIterator = arrayList.listIterator();
while (listIterator.hasNext()) {
String element = listIterator.next();
listIterator.set(element + "+");
}
System.out.print("Измененный arrayList: ");
System.out.println(arrayList);
System.out.print("Измененный arrayList в обратном порядке: ");
while (listIterator.hasPrevious()) {
String element = listIterator.previous();
System.out.print(element + " ");
}
}
}
В этом примере опять же создается список, который содержит обобщённый тип String
, и добавляются элементы A B C и D. Обратите внимание, что здесь для создания списка, используется метод asList
класса Arrays
. Дальше с помощью метода listIterator()
получаем ListIterator
. И точно так же, как мы действовали в предыдущих примерах - с помощью методов hasNext()
и next()
перебираем элементы. С помощью метода set()
мы видоизменяем каждый элемент, который получили. И добавляем каждому элементу плюс.
Во втором цикле while мы перебираем элементы коллекции, идя с конца в начало. Используем при этом методы hasPrevious()
и previous()
. Параллельно выводим на консоль состояние каждого элемента.
Давайте посмотрим, что будет на консоли после работы этой программы:
Измененный arrayList: [A+, B+, C+, D+]
Измененный arrayList в обратном порядке: D+ C+ B+ A+
11. Выводы
Итак, давайте подытожим, что мы рассмотрели в этом уроке:
- Используйте цикл for each, если нет необходимости изменять коллекцию.
- Используйте итератор тогда, когда при обходе коллекции у вас существует необходимость изменять эту коллекцию.
- Изменяйте коллекцию только используя методы итератора.
Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.