Javenue logo

Javenue

Программирование на Java

Информационные технологии

Паттерн Iterator в Java - Итератор

Iterator - это поведенческий шаблон проектирования, который позволяет пройтись по всем элементам некоторого составного объекта. Одним из важных условий при реализации паттерная является то, что итератор должен гарантировать нераскрытие внутреннего устройства объекта.

В Java итераторы очень активно используются в Collection Framework. Для этих целей создан специальный interface с одноименным названием java.util.Iterator. Интерфейс содержит следующие методы:

  • hasNext() - возвращает буелове значение в зависимости от того, есть ли еще элементы в коллекции.
  • next() - возвращает следующий элемент в коллекции. В соответствии с API метод должен бросать NoSuchElementException если все элементы уже пройдены.
  • remove() - удаляет элемент, который был возвращен последним вызовом next. К этому методу есть некоторые вопросы :) - почему собственно он находится в интерфейсе, ведь не каждая коллекция позволяет удалять элементы, в конце концов есть read only коллекции. Именно поэтому начиная с Java 1.8 метод remove описан как default с такой реализацией по умолчанию: default void remove() { throw new UnsupportedOperationException("remove"); }

Давайте опишем некоторый класс и реализуем для него Iterator.

Предположим у нас есть ужасный монстр, который состоит из головы, руки и ноги. Для каждой части есть сеттер, геттер, а так же для удобства метод, который говорит нам о наличии либо отсутствии части.

public class Monster {
    private Head head = new Head();
    private Hand theOnlyHand = new Hand();
    private Leg theOnlyLeg = new Leg();

    public Head getHead() { return head; }
    public void setHead(Head rightHead) { this.head = rightHead; }
    public boolean hasHead() { return head != null; }

    public Hand getTheOnlyHand() { return theOnlyHand; }
    public void setTheOnlyHand(Hand theOnlyHand) { this.theOnlyHand = theOnlyHand; }
    public boolean hasHand() { return theOnlyHand != null; }

    public Leg getTheOnlyLeg() { return theOnlyLeg; }
    public void setTheOnlyLeg(Leg theOnlyLeg) { this.theOnlyLeg = theOnlyLeg; }
    public boolean hasLeg() { return theOnlyLeg != null; }

    public interface Part { }
    public static class Head implements Part { }
    public static class Hand implements Part { }
    public static class Leg implements Part { }

    public static void main(String[] args) {
        // создадим монстра
        Monster monster = new Monster();
        // отрубим ему голову
        monster.setHead(null);
        // создадим итератор
        Iterator iterator = new MonsterIterator(monster);
        // есть ли у него еще части?
        System.out.println(iterator.hasNext()); // true
        // какая следующая часть
        System.out.println(iterator.next()); // Hand
        // а еще есть?
        System.out.println(iterator.hasNext()); // true
        // какая следующая часть
        System.out.println(iterator.next()); // Leg
        // а если отрубить
        iterator.remove();
        // а еще есть?
        System.out.println(iterator.hasNext()); // false
        // а что с ногой-то?
        System.out.println(monster.getTheOnlyLeg()); // null
    }
}

Класс итератора можно разместить в том же файле. Пусть индекс (index) отвечает за так называемый курсор в итераторе. Значение -1 будет установлено при создании нового объекта. Значения 0, 1 и 2 будут соответствовать голове, руке и ноге.

Реализуем методы hasNext, next и remove. Кстати, remove можно не реализовывать начиная с версии Java 1.8. В этом случае будет использована реализация по-умолчанию (смотрите начало статьи).

class MonsterIterator implements Iterator<Monster.Part>{
    private int index = -1;
    private Monster m;

    public MonsterIterator(Monster m) {
        this.m = m;
    }

    public boolean hasNext() {
        if (index == -1) return m.hasHead() || m.hasHand() || m.hasLeg();
        if (index == 0) return m.hasHand() || m.hasLeg();
        if (index == 1) return m.hasLeg();
        return false;
    }

    public Monster.Part next() {
        if (index == -1) {
            if (m.hasHead()) { index = 0; return m.getHead(); }
            if (m.hasHand()) { index = 1; return m.getTheOnlyHand(); }
            if (m.hasLeg()) { index = 2; return m.getTheOnlyLeg(); }
        }
        if (index == 0) {
            if (m.hasHand()) { index = 1; return m.getTheOnlyHand(); }
            if (m.hasLeg()) { index = 2; return m.getTheOnlyLeg(); }
        }
        if (index == 1) {
            if (m.hasLeg()) { index = 2; return m.getTheOnlyLeg(); }
        }
        throw new NoSuchElementException();
    }

    public void remove() {
        if (index == -1) throw new IllegalStateException();
        if (index == 0) 
            if (m.hasHead()) m.setHead(null); 
            else throw new IllegalStateException();
        if (index == 1) 
            if (m.hasHand()) m.setTheOnlyHand(null); 
            else throw new IllegalStateException();
        if (index == 2) 
            if (m.hasLeg()) m.setTheOnlyLeg(null); 
            else throw new IllegalStateException();
    }
}

Реализация итератора получилась не очень красивая. Просто я хотел показать, что итерировать можно не только по коллекции, а по любому составному объекту.

Для того, чтобы можно было удобнее создавать экземпляр итератора был введен интерфейс Iterable с методом iterator(). Можно описать класс Monster как реализующий этот интерфейс и реализовать метод iterator таким образом:

public class Monster implements Iterable<Monster.Part> {
    ...
    public Iterator<Part> iterator() {
        return new MonsterIterator(this);
    }
    ...
}

Кстати, интересный вопрос с подвохом для любителей паттернов проектирования: Примером какого паттерна является метод iterator() в java.util.Collection? Правильный ответ - Factory Method. Действительно, решение о том, какой именно итератор создавать отдается на откуп классам-наследникам (ArrayList, LinkedList, HashSet и так далее).

Если есть вопросы - пишите.



Комментариев: 1

  Выйти

  * для публикации комментариев нужно  

Alexander Shpilka:

Спасибо за статью. Для пущей красоты я бы ещё метод toString в каждой из Part (Head, Hand, Leg) переопределил.