Интерфейс Iterator в Java

В сегодняшнем нашем уроке мы продолжаем такую большую и интересную тему как Collection Framework и темой нашего сегодняшнего урока будут итераторы. Итак, что мы сегодня рассмотрим?

  1. Варианты перебора содержимого коллекции
  2. Интерфейс Iterator
  3. Методы интерфейса Iterator
  4. Пример использования методов hasNext() и next()
  5. Пример использования метода forEachRemaining()
  6. Возникновение ConcurrentModificationException
  7. Перебор коллекции с помощью цикла for each и интерфейс Iterable
  8. Интерфейс ListIterator
  9. Методы интерфейса ListIterator
  10. Пример использования интерфейса ListIterator
  11. Выводы

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, если нет необходимости изменять коллекцию.
  • Используйте итератор тогда, когда при обходе коллекции у вас существует необходимость изменять эту коллекцию.
  • Изменяйте коллекцию только используя методы итератора.
Читайте также:
Trustpilot
Trustpilot
Комментарии