JavaRush/Java блог/Java Developer/Паттерн Iterator

Паттерн Iterator

Статья из группы Java Developer
участников
Сегодня поговорим о том, что такое Iterator в Java и зачем он нужен.
Паттерн Iterator - 1
Как ты уже, вероятно, знаешь, в Java есть замечательный интерфейс Collection, реализующий интерфейс Iterator. Сразу оговорюсь, не следует путать интерфейс iterator с паттерном iterator в Java! И дабы внести ясность, для начала разберёмся именно с интерфейсом.
Дословно «Iterator» можно перевести как «переборщик». То есть это некая сущность, способная перебрать все элементы в коллекции. При этом она позволяет это сделать без вникания во внутреннюю структуру и устройство коллекций.
Представим на секунду, что iterator в Java отсутствует. В таком случае всем и каждому придётся нырнуть в самые глубины коллекций и по-настоящему разобраться, чем отличается, ArrayList от LinkedList и HashSet от TreeSet.

Методы, которые должен имплементировать Iterator

boolean hasNext() — если в итерируемом объекте (пока что это Collection) остались еще значение — метод вернет true, если значения кончились false. E next() — возвращает следующий элемент коллекции (объекта). Если элементов больше нет (не было проверки hasNext(), а мы вызвали next(), достигнув конца коллекции), метод бросит NoSuchElementException. void remove() — удалит элемент, который был в последний раз получен методом next(). Метод может бросить:
  • UnsupportedOperationException, если данный итератор не поддерживает метод remove() (в случае с read-only коллекциями, например)
  • IllegalStateException, если метод next() еще не был вызван, или если remove() уже был вызван после последнего вызова next().
Итак, iterator для List — самая распространенная имплементация. Итератор идет от начала коллекции к ее концу: смотрит есть ли в наличии следующий элемент и возвращает его, если таковой находится. На основе этого несложного алгоритма построен цикл for-each. Его расширение — ListIterator. Рассмотрим дополнительные методы java list iterator. Ты их, скорее всего, знаешь:
  • 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<String> list = new ArrayList<>();
list.add("Привет");
list.add("Обучающимся");
list.add("На");
list.add("JavaRush");
Теперь получим для него итератор и выведем в консоль все содержащиеся строки:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
Сейчас будет «узкое место»: Java Collections как ты, вероятно, знаешь (а если не знаешь, разберись), расширяют интерфейс Iterable, но это не означает, что только List, Set и Queue поддерживают итератор. Для java Map iterator также поддерживается, но его необходимо вызывать для Map.entrySet():
Map<String, Integer> map = new HashMap<>();
Iterator mapIterator = map.entrySet().iterator();
Тогда метод next() будет возвращать объект Entry, содержащий в себе пару «ключ»-«значение». Дальше все аналогично с List:
while (mapIterator.hasNext()) {
    Map.Entry<String, Integer> entry = 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() {
        return this.ending != null;
    }
Окей, у нас есть четыре перегруженных конструктора (для простоты, предположим, что суффикс у нас может быть только один). Существительное не может состоять из одной приставки, поэтому для конструктора с одним параметром будем устанавливать корень. Теперь напишем реализацию паттерна итератор: WordIterator, переопределяющий 2 метода: hasNext() и next():
public class WordIterator implements Iterator<Word.WordPart> {

    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 <= 0) {
            throw new NoSuchElementException("No more elements in this word!");
        }

        try {
            if (wordPartsCount == 4) {
                return word.getEnding();
            }
            if (wordPartsCount == 3) {
                return word.getSuffix();
            }
            if (wordPartsCount == 2) {
                return word.getPrefix();
            }
            return word.getRoot();
        } finally {
            wordPartsCount--;
        }
    }
}
Осталось назначить итератор классу Word:
public class Word implements Iterable<Word.WordPart> {@Override
	public Iterator<WordPart>iterator() {
    		return new WordIterator(this);
	}}
Теперь проведем морфемный разбор слова «перебежка»:
public class Main {
    public static void main(String[] args) {
        Word.Root root = new Word.Root("беж");
        Word.Prefix prefix = new Word.Prefix("пере");
        Word.Suffix suffix = new Word.Suffix("к");
        Word.Ending ending = new Word.Ending("a");

        Word word = new Word(root, prefix, suffix, ending);

        Iterator wordIterator = word.iterator();
        while (wordIterator.hasNext()) {
            Word.WordPart part = (Word.WordPart) wordIterator.next();
            System.out.println(part.getClass() + ": " + part.getWordPart());
        }
    }
}
Обрати внимание, в своей реализации паттерна iterator я выбрал следующий порядок вывода:
  1. окончание
  2. суффикс
  3. приставка
  4. корень
При проектировании собственного итератора, ты сам можешь задать алгоритм итерации, как захочешь. Успехов в обучении!
Комментарии (33)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
22 декабря 2022, 16:11
Меня одного смутила конструкция Word.WordPart? Или Word.Root? Это всë таки учебная статья, а автор фигачит все классы и интерфейсы внутри класса Word. А вруг кто захочет попробовать? В общем не делайте так. Есть такое понятие как best practices. Всегда делайте отдельные классы и интерфейсы.
Евгений
Уровень 19
25 апреля, 03:22
Так и произошло. Попытался воспроизвести. И смог только через внутренние классы и вложенный интерфейс. Для меня это из статьи было неочевидно.
Anonymous #2931973
Уровень 24
20 марта 2022, 21:04
На этом сайте не хватает строгой системы определений. Постоянно запутывают авторы статей.
Anonymous #2931973
Уровень 24
18 марта 2022, 15:08
"Как ты уже, вероятно, знаешь, в Java есть замечательный интерфейс Collection, реализующий интерфейс Iterator." Так, вроде бы Iterable, а не Iterator? Научите меня, пожалуйста, а то я что-то уже запутался.
fedyaka
Уровень 36
21 октября 2022, 10:41
Iterable не предоставляет никакого метода для изменения своих элементов, и мы не можем изменить их с помощью цикла for-each. Но Iterator позволяет удаление элементов из базовой коллекции во время итерации с remove() метод. Это все о различиях между Iterator и Iterable в Java. Но по сути если реализовывать интерфейс Iterable, то нужно будет переопределить метод iterator(), который создаёт объект Iterator в котором уже реализуются методы hasNext(), next(). Эта иерархия нужна для того, что бы можно было множество раз перебирать данные, потому что сам по себе объект Iterator может пройти по списку только 1 раз, обновить его можно только если заново создать iterator объект, что и делает метод iterator() реализации Iterable. Ну а если по простому: Iterator проходится по списку всего 1 раз, больше не может, нет функции возвращения к началу списка или прохода в обратную сторону. Iterable создаёт новый Iterator каждый раз при вызове метода iterator(). Надеюсь понятно объяснил, пхпхпх🙃
Damir
Уровень 37
26 января 2021, 05:48
Когдя вижу уровень 31 автора статьи я хочу борость все к чертям собачьим... Если б я даже знал все это, я бы не объяснил все это так просто. А ведь посмореть как проходить по словарям я возвращался раз 10.
fedyaka
Уровень 36
21 октября 2022, 10:25
могу лишь сказать одно, чем дальше идёшь, тем легче становятся прошедшие темы и при повторении будет всё очень просто, поэтому удачи тебе дальше двигаться! не бросай учиться и всё получиться!
Deniska Ученик чародея в тредевятом царстве
23 декабря 2020, 16:59
когда-нибудь мы все это будем знать
Jet
Уровень 24
8 июня 2022, 02:56
Ну и как оно сейчас? Спустя полтора года познал вот это вот всё?
Anonymous #3262899
Уровень 3
10 ноября 2023, 19:57
а у тебя как)
Mykola Bielousov QA Automation Engineer в PDI Technologies
25 июля 2020, 11:27
public static Set<Object> join(Set<Cat> cats, Set<Dog> dogs) { //напишите тут ваш код Set <Object> pets = new HashSet<>(); pets.addAll(cats); pets.addAll(dogs); return pets; } как удалить через итератор только котов?
Alexander Lukyanov
Уровень 9
8 сентября 2023, 07:10
А зачем в данном случае удалять котов через перебор? Они же все в одном наборе.
Николай Т.
Уровень 40
20 июля 2020, 11:53
Получается что код в примере: Map<String, Integer> map = new HashMap<>(); Iterator mapIterator = map.entrySet().iterator(); Тогда метод next() будет возвращать объект Entry, содержащий в себе пару «ключ»-«значение». Дальше все аналогично с List: while (mapIterator.hasNext()) { Map.Entry<String, Integer> entry = mapIterator.next(); System.out.println("Key: " + entry.getKey()); System.out.println("Value: " + entry.getValue()); } С ошибкой, т.к. итератору не указано какого он типа.
wert4527
Уровень 22
5 июня 2020, 16:29
что такое @Override
Дмитрий
Уровень 41
2 августа 2020, 08:26
Переопределение
Alexander Lukyanov
Уровень 9
8 сентября 2023, 07:19
Указывает, что помеченный так метод объявлен в имплементированном интерфейсе или родительском классе, а в данном классе определяется или переопределяется. Хотя среда разработки будет следить за простановкой таких аннотаций, отсутствие '@Override` не приводит к ошибке компиляции, т.е., строго говоря, аннотация является информационной.
204sa
Уровень 28
8 мая 2020, 16:27
"в Java есть замечательный интерфейс Collection, реализующий интерфейс Iterator" почему интерфейс Iterator, если Collection реализует интерфейс Interable??
Леонель Хантер
Уровень 16
2 мая 2020, 11:22
Пример просто бомба)))) Ничего не объясняет)
Alexander Lukyanov
Уровень 9
8 сентября 2023, 07:21
Согласен. Несколько лет занимаясь Явой, я конечно примерно понял, что тут имеется в виду, но наглядного прояснения, которого искал, здесь не встретил.