JavaRush /Java блог /Random UA /Паттерн Iterator
Алексей Дмитревский
31 рівень
Москва

Паттерн Iterator

Стаття з групи Random UA
Сьогодні поговоримо про те, що таке 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. корінь
При проектуванні власного ітератора ти сам можеш задати алгоритм ітерації, як захочеш. Успіхів у навчанні!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ