Architect's internal approach, well-known but not known iterator pattern in detail

Iterator Pattern, also known as Cursor Pattern, provides a way to sequentially access a collection or container object element without exposing its internal representation.The iterator pattern provides a traversal behavior for different containers, regardless of the composition of the elements within them.The essence of the iterator pattern is to pull out the set object iteration behavior into the iterator and provide a consistent access interface.

I. Scenarios for Iterator Mode Application

Iterator mode is also widely used in our life, such as the conveyor belt in the logistics system, which is packed into one box and has a unified two-dimensional code regardless of what is being delivered.This way we don't need to care about what's in the box. We only need one destination to check and send when distributing.For example, when we ride on vehicles, we usually brush our cards or brush our faces to enter the station. We don't need to care about personalized information such as male or female, disabled or normal people.

We call the aggregation of objects together an Aggregate, which is a container object that can contain a set of objects.Different collections may have different aggregation and structure of their internal elements, while iterator mode shields the internal elements from getting details, provides consistent element access behavior for the outside, decouples the coupling between element iteration and collection objects, and provides different iterators, provides different order of element access behavior for the same object, and extends the collection-to-element iteration function.Close and close principle.

Iterator mode is used in the following scenarios:

  • Accessing the contents of a collection object without exposing its internal representation;
  • Provides a unified access interface for traversing different collection structures.

There are four main roles in the iterator pattern:

  • Iterator: The interface responsible for defining the access and traversal of elements;
  • ConcreteIterator: Provides specific element traversal behavior;
  • Aggregate: Responsible for defining interfaces that provide specific iterators;
  • ConcreteAggregate: Create a specific iterator.

1.1 Customized Iterators

To take a course as an example, create a collection of courses in which each element is a course object, and then customize an iterator to read out the information of each course object.First create the Course class for the Collection Elements Course object:

public class Course {

    private String name;

    public Course(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Then create a custom interface Iterator:

public interface Iterator<E> {

    E next();

    boolean hasNext();

}

Next, create the CourseAggregate interface for the course collection:

public interface CourseAggregate {

    void add(Course course);

    void remove(Course course);

    Iterator<Course> iterator(); 

}

Then implement the iterator interface and the course interface:

public class IteratorImpl<E> implements Iterator<E> {

    private List<E> list;

    private int cursor;

    private E element;

    public IteratorImpl(List<E> list) {
        this.list = list;
    }

    @Override
    public E next() {
        System.out.println("The current location is:" + cursor);
        element = list.get(cursor);
        cursor ++ ;
        return element;
    }

    @Override
    public boolean hasNext() {
        if(cursor > list.size() - 1) {
            return false;
        }
        return true;
    }
}

The CourseAggregateImpl class is implemented in the course collection:

public class CourseAggregateImpl implements CourseAggregate {

    private List list;

    public CourseAggregateImpl(List list) {
        this.list = new ArrayList();
    }

    @Override
    public void add(Course course) {
        list.add(course);
    }

    @Override
    public void remove(Course course) {
        list.remove(course);
    }

    @Override
    public Iterator<Course> iterator() {
        return new IteratorImpl<Course>(list);
    }
}

Test main method:

public static void main(String[] args) {

    Course chinese = new Course("Chinese");
    Course math = new Course("Mathematics");
    Course english = new Course("English");

    CourseAggregate courseAggregate = new CourseAggregateImpl();
    courseAggregate.add(chinese);
    courseAggregate.add(math);
    courseAggregate.add(english);

    courseAggregate.remove(math);

    Iterator<Course> courseIterator = courseAggregate.iterator();
    while (courseIterator.hasNext()) {
        System.out.println(courseIterator.next().getName());
    }

}

2. Iterator pattern in source code

Iterator in 2.1 JDK

The Iterator interface source code is as follows:

public interface Iterator<E> {
    /**
     * Returns {@code true} if the iteration has more elements.
     * (In other words, returns {@code true} if {@link #next} would
     * return an element rather than throwing an exception.)
     *
     * @return {@code true} if the iteration has more elements
     */
    boolean hasNext();

    /**
     * Returns the next element in the iteration.
     *
     * @return the next element in the iteration
     * @throws NoSuchElementException if the iteration has no more elements
     */
    E next();

    /**
     * Removes from the underlying collection the last element returned
     * by this iterator (optional operation).  This method can be called
     * only once per call to {@link #next}.  The behavior of an iterator
     * is unspecified if the underlying collection is modified while the
     * iteration is in progress in any way other than by calling this
     * method.
     *
     * @implSpec
     * The default implementation throws an instance of
     * {@link UnsupportedOperationException} and performs no other action.
     *
     * @throws UnsupportedOperationException if the {@code remove}
     *         operation is not supported by this iterator
     *
     * @throws IllegalStateException if the {@code next} method has not
     *         yet been called, or the {@code remove} method has already
     *         been called after the last call to the {@code next}
     *         method
     */
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    /**
     * Performs the given action for each remaining element until all elements
     * have been processed or the action throws an exception.  Actions are
     * performed in the order of iteration, if that order is specified.
     * Exceptions thrown by the action are relayed to the caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{@code
     *     while (hasNext())
     *         action.accept(next());
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

From the code above, we can see that the two main methods define hasNext() and next() methods exactly as we have written.In addition, from the code above, we see that the remove() method implements a similar pattern.In fact, we've seen it in combination mode.There seems to be some similarity between the iterator mode and the combination mode.The combination mode solves the problem of access ports at all levels of a unified tree structure, while the iterator mode solves the problem of unifying the elements of a set of objects to traverse ports.Although their adapting scenarios are different, the core concepts are common.

The implementation class of the Iterator interface, which implements the class Itr internally within the ArrayList class, implements the Iterator interface:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

The next() and hasNext() methods are easy to implement, and several iterators in the ArrayList class further extend Itr to look at the source code of the ListItr class:

private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

3. Advantages and disadvantages of iterator mode

Advantage:

  • Polymorphic iteration: Provides a consistent traversal interface for different aggregation structures, that is, an iteration interface can access different collection objects;
  • Simplify set object connections: The iterator pattern extracts elements that the set object itself should provide iteratively into the iterator so that the set object does not have to be concerned with specific iteration behavior;
  • Element iteration functions are diverse: each collection object can provide one or more different iterators so that the same element aggregation structure can have different iteration behavior;
  • Decoupling iteration and set: The iterator mode encapsulates a specific iteration algorithm, and changes in the iteration algorithm do not affect the architecture of the set object.

Disadvantages:

  • For simpler traversals (arrays or ordered lists), iterator traversal is cumbersome.

Keywords: Programming JDK

Added by warmwind on Thu, 19 Mar 2020 09:20:43 +0200