迭代器模式与Java Iterator源码

。_饼干妹妹 提交于 2020-02-08 16:13:43

迭代器模式

迭代器模式是一种将集合的增删改操作与集合的顺序遍历操作分离的设计模式。集合只负责增删改操作,迭代器对于集合的内部类,专门负责顺序遍历。Java的Iterator是迭代器模式的经典实现。笔者jdk版本是11.0.4,不同版本的jdk的Iterator相关类及其实现有所不同,下面以jdk11.0.4为例。

Iterator

jdk定义了一个Iterator接口,声明了hasNext, next, remove方法,分别用于检查是否结束遍历、遍历下一个元素、删除元素的操作。

/**
 * 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

Iterable接口是可迭代对象的接口,其中iterator方法返回一个迭代器对象。

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

集合类的基类接口Collection中,实现了Iterable接口。

public interface Collection<E> extends Iterable<E>

Itr

在jdk11.0.4中,List集合的抽象基类AbstractList和具体的ArrayList类中都各自实现了迭代器内部类Itr, ListItr.
AbstractList.Itr为例,属性cursor是下一个元素的索引,lastRet是最后一次返回的元素的索引,一般来说是cursor - 1expectedModCount用于检查ConcurrentModification异常(下面会详述)。可以看到,相比于大家很容易想到的实现,jdk对于nextremove的实现中只是多了对于ConcurrentModification异常的检查checkForComodification(下面会详述).

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只是对于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是对Itr的功能扩展,提供了添加元素、反向遍历等功能。但是ListItr只能用于AbstractList的子类,而Itr则可以用于所有Collection的子类。下面是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遍历删除的ConcurrentModificationException及其解决

使用foreach风格的遍历的同时使用集合对象的remove方法删除对象,可能会引发ConcurrentModificationException异常,例如如下代码Main.java会抛出该异常。

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.javajava Main输出:

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)

使用jdk自带的反编译工具javap查看foreach风格的遍历的实现(javap -v Main),可以看到foreach遍历实际就是使用Iterator进行遍历:42: invokeinterface #10, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z, 51: invokeinterface #11, 1 // InterfaceMethod java/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"

如果不使用foreach风格的遍历,直接使用Iterator遍历,并使用Iteratorremove方法进行删除,则不会引发异常,程序可以正常工作。

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

为什么使用集合类的remove方法会抛ConcurrentModificationException异常而Iteratorremove方法不会呢?这就需要再次回到jdk源码中。
ArrayList.Itr.remove()方法:

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)方法:

/**
    * 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;
}

相比于ArrayListremove(Object)方法,Itrremove()方法会检查Itr类的expectedModCount属性与外部List类的modCount属性是否相等(checkForComodification();),并在删除元素后保证expectedModCountmodCount相等(expectedModCount = modCount;)。这样就解决了ConcurrentModificationException异常问题。

  • Itr类的checkForComodification方法中,检查Itr类的expectedModCount属性与外部List类的modCount属性是否相等,从而决定是否抛出ConcurrentModificationException异常:
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!