Iterator Mode and Java Iterator Source

Keywords: Java JDK Attribute

Iterator mode

Iterator mode is a design mode that separates the addition and deletion of a set from the sequential traversal of the set.Collections are responsible for additions, deletions, and iterators are responsible for sequential traversal of the collection's internal classes.Java's Iterator is a classic implementation of the iterator pattern.The author's version of JDK is 11.0.4. The Iterator related classes and their implementations are different in different versions of jdk. Here is an example of jdk11.0.4.

Iterator

jdk defines an Iterator interface and declares hasNext, next, and remove methods, which are used to check whether to end the traversal, traverse the next element, and delete the element, respectively.

/**
 * An iterator over a collection.  {@code Iterator} takes the place of
 * {@link Enumeration} in the Java Collections Framework.  Iterators
 * differ from enumerations in two ways:
 *
 * <ul>
 *      <li> Iterators allow the caller to remove elements from the
 *           underlying collection during the iteration with well-defined
 *           semantics.
 *      <li> Method names have been improved.
 * </ul>
 *
 * <p>This interface is a member of the
 * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
 * Java Collections Framework</a>.
 *
 * @apiNote
 * An {@link Enumeration} can be converted into an {@code Iterator} by
 * using the {@link Enumeration#asIterator} method.
 *
 * @param <E> the type of elements returned by this iterator
 *
 * @author  Josh Bloch
 * @see Collection
 * @see ListIterator
 * @see Iterable
 * @since 1.2
 */
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}.
     * <p>
     * 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, unless an overriding class has specified a
     * concurrent modification policy.
     * <p>
     * The behavior of an iterator is unspecified if this method is called
     * after a call to the {@link #forEachRemaining forEachRemaining} 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.
     * <p>
     * The behavior of an iterator is unspecified if the action modifies the
     * collection in any way (even by calling the {@link #remove remove} method
     * or other mutator methods of {@code Iterator} subtypes),
     * unless an overriding class has specified a concurrent modification policy.
     * <p>
     * Subsequent behavior of an iterator is unspecified if the action throws an
     * exception.
     *
     * @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());
    }
}

Iterable

The Iterable interface is an interface to an iterator object, where the iterator method returns an iterator object.

public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();

The Iterable interface is implemented in the collection class's base class interface Collection.

public interface Collection<E> extends Iterable<E>

Itr

In jdk11.0.4, both the abstract base class AbstractList of the List collection and the specific ArrayList class implement the iterator internal class Itr, ListItr.
For example, AbstractList.Itr, the attribute cursor is the index of the next element, lastRet is the index of the last returned element, generally cursor-1, and expectedModCount is used to check for ConcurrentModificationExceptions (described below).As you can see, jdk's implementation of Next and remove only checkForComodification for ConcurrentModificationExceptions is more than you can easily imagine.

private class Itr implements Iterator<E> {
    /**
        * Index of element to be returned by subsequent call to next.
        */
    int cursor = 0;

    /**
        * Index of element returned by most recent call to next or
        * previous.  Reset to -1 if this element is deleted by a call
        * to remove.
        */
    int lastRet = -1;

    /**
        * The modCount value that the iterator believes that the backing
        * List should have.  If this expectation is violated, the iterator
        * has detected concurrent modification.
        */
    int expectedModCount = modCount;

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

    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

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

        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }

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

ArrayList.Itr is only an optimized re-implementation of AbstractList.Itr.

/**
    * An optimized version of AbstractList.Itr
    */
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;

    // prevent creating a synthetic constructor
    Itr() {}

    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
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i < size) {
            final Object[] es = elementData;
            if (i >= es.length)
                throw new ConcurrentModificationException();
            for (; i < size && modCount == expectedModCount; i++)
                action.accept(elementAt(es, i));
            // update once at end to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }
    }

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

ListItr

ListItr is an extension of Itr, providing functions such as adding elements, reverse traversal, and so on.However, ListItr can only be used for subclasses of AbstractList, while Itr can be used for subclasses of all Collection s.Below is an example of AbstractList.ListItr.

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

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

    public E previous() {
        checkForComodification();
        try {
            int i = cursor - 1;
            E previous = get(i);
            lastRet = cursor = i;
            return previous;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public int nextIndex() {
        return cursor;
    }

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

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

        try {
            AbstractList.this.set(lastRet, e);
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

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

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

foreach Traverses Deleted ConcurrentModificationException and its Resolution

Deleting an object using the remove method of a collection object while traversing in the foreach style may raise a ConcurrentModificationException exception, such as thrown by Main.java in the following code.

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> arr = new ArrayList<>();
        arr.add("1");
        arr.add("2");
        arr.add("12");
        for (String num: arr) {
            System.out.println(num.length());
            if (num.length() == 2) {
                arr.remove(num);
            }
        }
    }
}

javac Main.java, java Main output:

1
1
2
Exception in thread "main" java.util.ConcurrentModificationException
        at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
        at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
        at Main.main(Main.java:9)

Using jdk's own decompiling tool, javap, to view the implementation of foreach-style traversal (javap-v Main), you can see that foreach traversal is actually using Iterator: 42: invokeinterface #10, 1 // InterfaceMethod java/util/Iterator.hasNext:() Z, 51: invokeinterface #11, 1 // Interfacejava/util/Iterator.next:() Ljava/lang/Object;

public class Main
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #17                         // Main
  super_class: #18                        // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #18.#29        // java/lang/Object."<init>":()V
   #2 = Class              #30            // java/util/ArrayList
   #3 = Methodref          #2.#29         // java/util/ArrayList."<init>":()V
   #4 = String             #31            // 1
   #5 = Methodref          #2.#32         // java/util/ArrayList.add:(Ljava/lang/Object;)Z
   #6 = String             #33            // 2
   #7 = String             #34            // 12
   #8 = String             #35            // 3
   #9 = Methodref          #2.#36         // java/util/ArrayList.iterator:()Ljava/util/Iterator;
  #10 = InterfaceMethodref #26.#37        // java/util/Iterator.hasNext:()Z
  #11 = InterfaceMethodref #26.#38        // java/util/Iterator.next:()Ljava/lang/Object;
  #12 = Class              #39            // java/lang/String
  #13 = Fieldref           #40.#41        // java/lang/System.out:Ljava/io/PrintStream;
  #14 = Methodref          #12.#42        // java/lang/String.length:()I
  #15 = Methodref          #43.#44        // java/io/PrintStream.println:(I)V
  #16 = Methodref          #2.#45         // java/util/ArrayList.remove:(Ljava/lang/Object;)Z
  #17 = Class              #46            // Main
  #18 = Class              #47            // java/lang/Object
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               StackMapTable
  #26 = Class              #48            // java/util/Iterator
  #27 = Utf8               SourceFile
  #28 = Utf8               Main.java
  #29 = NameAndType        #19:#20        // "<init>":()V
  #30 = Utf8               java/util/ArrayList
  #31 = Utf8               1
  #32 = NameAndType        #49:#50        // add:(Ljava/lang/Object;)Z
  #33 = Utf8               2
  #34 = Utf8               12
  #35 = Utf8               3
  #36 = NameAndType        #51:#52        // iterator:()Ljava/util/Iterator;
  #37 = NameAndType        #53:#54        // hasNext:()Z
  #38 = NameAndType        #55:#56        // next:()Ljava/lang/Object;
  #39 = Utf8               java/lang/String
  #40 = Class              #57            // java/lang/System
  #41 = NameAndType        #58:#59        // out:Ljava/io/PrintStream;
  #42 = NameAndType        #60:#61        // length:()I
  #43 = Class              #62            // java/io/PrintStream
  #44 = NameAndType        #63:#64        // println:(I)V
  #45 = NameAndType        #65:#50        // remove:(Ljava/lang/Object;)Z
  #46 = Utf8               Main
  #47 = Utf8               java/lang/Object
  #48 = Utf8               java/util/Iterator
  #49 = Utf8               add
  #50 = Utf8               (Ljava/lang/Object;)Z
  #51 = Utf8               iterator
  #52 = Utf8               ()Ljava/util/Iterator;
  #53 = Utf8               hasNext
  #54 = Utf8               ()Z
  #55 = Utf8               next
  #56 = Utf8               ()Ljava/lang/Object;
  #57 = Utf8               java/lang/System
  #58 = Utf8               out
  #59 = Utf8               Ljava/io/PrintStream;
  #60 = Utf8               length
  #61 = Utf8               ()I
  #62 = Utf8               java/io/PrintStream
  #63 = Utf8               println
  #64 = Utf8               (I)V
  #65 = Utf8               remove
{
  public Main();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String 1
        11: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        14: pop
        15: aload_1
        16: ldc           #6                  // String 2
        18: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        21: pop
        22: aload_1
        23: ldc           #7                  // String 12
        25: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        28: pop
        29: aload_1
        30: ldc           #8                  // String 3
        32: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        35: pop
        36: aload_1
        37: invokevirtual #9                  // Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
        40: astore_2
        41: aload_2
        42: invokeinterface #10,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
        47: ifeq          87
        50: aload_2
        51: invokeinterface #11,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        56: checkcast     #12                 // class java/lang/String
        59: astore_3
        60: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        63: aload_3
        64: invokevirtual #14                 // Method java/lang/String.length:()I
        67: invokevirtual #15                 // Method java/io/PrintStream.println:(I)V
        70: aload_3
        71: invokevirtual #14                 // Method java/lang/String.length:()I
        74: iconst_2
        75: if_icmpne     84
        78: aload_1
        79: aload_3
        80: invokevirtual #16                 // Method java/util/ArrayList.remove:(Ljava/lang/Object;)Z
        83: pop
        84: goto          41
        87: return
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 7: 15
        line 8: 22
        line 9: 29
        line 10: 36
        line 11: 60
        line 12: 70
        line 13: 78
        line 15: 84
        line 16: 87
      StackMapTable: number_of_entries = 3
        frame_type = 253 /* append */
          offset_delta = 41
          locals = [ class java/util/ArrayList, class java/util/Iterator ]
        frame_type = 42 /* same */
        frame_type = 250 /* chop */
          offset_delta = 2
}
SourceFile: "Main.java"

If you do not use foreach-style traversal, use Iterator traversal directly, and delete it using the Iterator's remove method, no exceptions will be raised and the program will work correctly.

import java.util.ArrayList;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> arr = new ArrayList<>();
        arr.add("1");
        arr.add("2");
        arr.add("12");
        // for (String num: arr) {
        //     System.out.println(num.length());
        //     if (num.length() == 2) {
        //         arr.remove(num);
        //     }
        // }
        Iterator<String> iter = arr.iterator();
        while (iter.hasNext()) {
            String num = iter.next();
            System.out.println(num.length());
            if (num.length() == 2) {
                iter.remove();
            }
        }
    }
}

Why does using the remove method of a collection class throw a ConcurrentModificationException exception but not the remove method of an Iterator?This requires going back to the jdk source again.
ArrayList.Itr.remove() method:

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();
    }
}

ArrayList.remove(Object) method:

/**
    * Removes the first occurrence of the specified element from this list,
    * if it is present.  If the list does not contain the element, it is
    * unchanged.  More formally, removes the element with the lowest index
    * {@code i} such that
    * {@code Objects.equals(o, get(i))}
    * (if such an element exists).  Returns {@code true} if this list
    * contained the specified element (or equivalently, if this list
    * changed as a result of the call).
    *
    * @param o element to be removed from this list, if present
    * @return {@code true} if this list contained the specified element
    */
public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}

/**
    * Private remove method that skips bounds checking and does not
    * return the value removed.
    */
private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

Compared to the ArrayList remove(Object) method, the Itr remove() method checks whether the expectedModCount property of the Itr class is equal to the modCount property of the external List class (checkForComodification(); and ensures that the expectedModCount is equal to the modCount after deleting the element (expectedModCount = modCount;).This resolves the ConcurrentModificationException exception problem.

  • In the checkForComodification method of the Itr class, check whether the expectedModCount property of the Itr class is equal to the modCount property of the external List class to determine whether the ConcurrentModificationException exception is thrown:
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
707 original articles were published, 140 were praised, 240,000 visits+
His message board follow

Posted by gsv2com on Fri, 07 Feb 2020 20:47:32 -0800