Iterator of behavior pattern

1 General

Iterator pattern is one of the most common design patterns that people who have used Java collections have been exposed to.

2 iterator mode

Collection is a common type in programming. They are containers for storing elements. There are many types of collections, such as list, set, stack, tree, etc. for users, there is a unified way to traverse the elements in the collection. In addition, users sometimes need different ways to traverse elements, such as depth first and breadth first. If you blindly add traversal methods to a collection, it will make the collection more and more complex. The iterator pattern provides a solution for this: providing independent iterator objects to provide the ability to traverse elements.

The iterator hides the details at the bottom of the collection and provides a set of uniform element access methods. If you need a new algorithm to traverse the elements, you only need to create a new iterator object without modifying the collection object.

3 cases

The Collection in JDK applies the iterator pattern well. Iterator interface in JDK:

public interface Iterator<E> {
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

     * [@param](https://my.oschina.net/u/2303379) action The action to be performed for each element
     * [@throws](https://my.oschina.net/throws) NullPointerException if the specified action is null
     * [@since](https://my.oschina.net/u/266547) 1.8
     */
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

There are two main methods: hasNext() and next(). The former is used to determine whether there are any remaining elements in the collection, and the latter is used to get the next element.

The general usage mode is:

Iterator iterator = colelction.iterator();
while(iterator.hasNext()) {
    Object element = iterator.next();
    // do something with the element
}

How does the collection integrate the Iterator interface? Take ArrayList for example:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // Returns an iterator object that traverses the elements in the List
    public Iterator<E> iterator() {
        return new Itr();
    }

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

        Itr() {}

        // If the cursor is not equal to the List length, there are elements not traversed
        public boolean hasNext() {
            return cursor != size;
        }

        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();
            // Add 1 to the cursor to get to the next element
            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](https://my.oschina.net/u/1162528)
        @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();
        }

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

A simple implementation: every time you call the next() method, get the current element, and add 1 to the cursor. If the cursor is less than the length of the list, it means that it is not the end; if the cursor is equal to the length of the list, it means that all elements have been traversed.

It is easy to speculate that the iterator of LinkedList accesses elements one by one in the way of linked list.

Look at the iterator example in TreeSet:

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable {
    private transient NavigableMap<E,Object> m;
    /**
     * Returns an iterator over the elements in this set in ascending order.
     *
     * @return an iterator over the elements in this set in ascending order
     */
    public Iterator<E> iterator() {
        return m.navigableKeySet().iterator();
    }

    /**
     * Returns an iterator over the elements in this set in descending order.
     *
     * @return an iterator over the elements in this set in descending order
     * @since 1.6
     */
    public Iterator<E> descendingIterator() {
        return m.descendingKeySet().iterator();
    }
}

For TreeSet, the default iterator method iterator() is ascending. The descending iterator() method is called to get the iterator in descending order. If you need a new element traversal implementation, you only need to add a corresponding iterator, without changing the original storage logic of TreeSet.

4 Summary

The iterator pattern provides a unified access to the elements in the collection. It decouples element traversal and element storage, which is a very important design pattern.

The github address of the example in this paper

Tags: Programming Java JDK less

Posted on Mon, 04 May 2020 20:01:23 -0400 by palpie