Java源码分析 AbstractList的迭代器实现(Itr和ListItr)

ItrListItr的简介

AbstractList的有两个内部类实现了迭代器,一个内部类Itr实现了Iterator接口,一个内部类ListItr实现了ListIterator接口,后者其实就是前者的升级版罢了,多了一些额外操作。

下面先来看一下返回这两个内部类实例的方法的注释:

public Iterator iterator() { return new Itr(); }前面的重要注释摘抄如下(字面意思请自行谷歌翻译,中文只进行重要的解释):

This implementation returns a straightforward implementation of the iterator interface, relying on the backing list’s size(), get(int), and remove(int) methods.
Note that the iterator returned by this method will throw an UnsupportedOperationException in response to its remove method unless the list’s remove(int) method is overridden.
This implementation can be made to throw runtime exceptions in the face of concurrent modification, as described in the specification for the (protected) #modCount field.

  • 这个方法返回了iterator接口的实现类。backing list我理解是该迭代器持有的外部类对象(list对象),因为Itr是一个成员内部类,所以必然会持有外部类对象。而Itr会调用到size(), get(int), and remove(int)方法,所以说依赖于它们。
  • 当外部类对象(list对象)并没有实现remove(int)方法时,由于默认实现是直接抛出UnsupportedOperationException异常(AbstractList里面的实现),所以导致迭代器的remove也会抛异常。
  • 当遇到并发修改时(比如add、remove),可能抛出runtime exception,通过#modCount实现,这是外部类对象(list对象)的成员,别的线程可能同时也会进行修改动作。

public ListIterator listIterator(final int index) { rangeCheckForAdd(index); return new ListItr(index); }的注释如下:

This implementation returns a straightforward implementation of the ListIterator interface that extends the implementation of the Iterator interface returned by the iterator() method. The ListIterator implementation relies on the backing list’s get(int), set(int, E), add(int, E) and remove(int) methods.
Note that the list iterator returned by this implementation will throw an UnsupportedOperationException in response to its remove, set and add methods unless the list’s remove(int), set(int, E), and add(int, E) methods are overridden.
This implementation can be made to throw runtime exceptions in the face of concurrent modification, as described in the specification for the (protected) #modCount field.

  • 可见这个迭代器依赖的操作更多了,它还依赖外部类list对象的set(int, E), add(int, E)方法。
  • 上面注释没讲,这个迭代器除了可以往前走next(),它还可以往回走previous()

ItrListItr的具体实现

这就上源码分析吧,具体细节看注释:

    private class Itr implements Iterator<E> {
        /**
         * 下一次调用next将返回的元素索引
         */
        int cursor = 0;

        /**
         * 最近一次调用next或者previous返回的元素的索引。
         * 会被置为-1,当remove的调用后删除了lastRet代表的元素。
         */
        int lastRet = -1;

        /**
         * expectedModCount字段是迭代器认为外部类list对象应该具有的值。
         * 这个字段在迭代器初始化时就得到了。如果迭代器使用过程中,检测到
         * 这个值和外部类对象的值不一样,则发生并发的修改。
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            //会间接调用到外部类对象的size方法,没有默认的抛异常的实现,外部类list必须实现
            return cursor != size();
        }

        public E next() {
            checkForComodification();
            try {
                int i = cursor;//既然cursor是下一次next的返回索引,那就先取出来给i
                E next = get(i);//调用到外部类AbstractList的get方法
                lastRet = i;//既然已经用get函数访问了i索引,所以lastRet置为i
                cursor = i + 1;//因为方向是next,所以cursor = cursor + 1
                return next;
            } catch (IndexOutOfBoundsException e) {
                //get函数可能抛出IndexOutOfBoundsException,
                //可能是因为外部类对象的modCount已经被改变,进而抛出ConcurrentModificationException
                //但这也可能是因为迭代器已经到达末尾,所以checkForComodification()不会抛出异常
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)//说明上一次执行了迭代器的删除后,还没有进行过迭代器的移动,这种情况不允许删除
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);//调用外部类方法remove
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;//删除后,lastRet上一次访问的元素已经不存在,因为已经被删除了
                //若不置-1,那么lastRet指向的是删除前lastRet+1那个元素,总之是不对的
                expectedModCount = modCount;//删除后获取新的modCount
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
  • 其实可以想象迭代器的位置是在某个元素之间的中间位置,进一步说,迭代器的位置是在cursor索引元素之前的中间位置(不要理解成,是在lastRet和cursor之间的中间位置,因为后面介绍的ListItrprevious()方法会使得lastRet和cursor相同)。
  • next()的逻辑和后面介绍ListItrprevious()的逻辑,可以看出,在移动迭代器时,是按照cursor的位置来移动的。next()会使得cursor加1,previous()会使得cursor减1。
  • lastRet代表的是最近一次调用next()或者previous()返回的元素的索引。所以Itr初始化的时候lastRet的初值为-1,因为刚初始化的迭代器还没有返回过任何元素,所以置为-1,代表一个不可能的索引。
  • remove()执行后,会把lastRet置为-1,这很合理,因为lastRet索引元素已经被删除。

Java源码分析 AbstractList的迭代器实现(Itr和ListItr)_第1张图片

  • 如上图所示,绿色节点代表被删元素,在remove()执行后,迭代器的位置的往回走了一步(其实就是cursor减1了)。如果cursor不减1,那么迭代器将会在一个错误的位置上。
  • remove()的逻辑里面,有一句if (lastRet < cursor)判断成功后才会cursor--,这是因为后面介绍的ListItrprevious()方法会使得lastRet和cursor相同。

再看ListItr的源码吧:

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

        public boolean hasPrevious() {
            return cursor != 0;//只要cursor不等于0,就可以往回走
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;//往回走,所以cursor-1给局部变量
                E previous = get(i);
                lastRet = cursor = i;//因为get的参数是i,所以lastRet肯定也应该为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)//说明执行了remove或add后,还没有进行过next或previous操作
                throw new IllegalStateException();
            checkForComodification();

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

        public void add(E e) {
            //add不用关心lastRet,即不在乎之前有没有remove或add
            checkForComodification();

            try {
                int i = cursor;
                AbstractList.this.add(i, e);
                lastRet = -1;
                cursor = i + 1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }    
  • 首先多了hasPrevious()previous()方法,previous()方法会让cursor减1,然后让lastRet和cursor相同。

Java源码分析 AbstractList的迭代器实现(Itr和ListItr)_第2张图片

  • 本章第一个图的“刚执行next操作”的状态下,如果再执行previous操作,就会变成上图的“刚执行previous操作”的状态。正因为previous()方法会让lastRet和cursor相同,所以在remove()的逻辑里才会去判断if (lastRet < cursor)。如果此时lastRet < cursor成立,说明刚执行了next操作;如果此时lastRet < cursor不成立(即lastRet = cursor),说明刚执行了previous操作。
  • add(E e)方法会往cursor索引位置添加元素,添加之前cursor索引元素以及后续元素会往右移动一步,此方法执行后会使得lastRet为-1,但此方法并不关心lastRet是否为-1,即不在乎是不是刚执行了remove()add()方法。从下图可以看出(绿色节点为添加元素),无论连续执行多少次add(E e)方法,cursor实际指向元素没有发生变化。下图第一个状态没有标注出lastRet的位置,因为此方法不关心lastRet。

Java源码分析 AbstractList的迭代器实现(Itr和ListItr)_第3张图片

  • set(E e)方法在执行前,必须判断出lastRet不为-1。lastRet为-1,说明刚执行过remove或add操作。此方法不会改变lastRet的值,因为此方法只是在替换元素。

总结

  • 除了lastRet为-1的情况外(刚执行了remove或add),lastRet和cursor的关系只可能是:lastRet + 1 = cursor,或者lastRet = cursor。
  • 迭代器的位置可以想象为cursor索引元素和cursor-1索引元素之间的中间位置。即迭代器关心的是cursor的位置。
  • lastRet的含义就是上一次执行next()previous()返回的那个元素的索引,所以next()previous()会对lastRet作出妥当的处理。
  • 之所以remove()方法中要把lastRet置为-1,是因为执行完remove()方法后原来lastRet指向的元素已经被删除掉了,所以lastRet需要指向一个不可能的元素。之所以remove()方法中最开始要判断lastRet是否为-1,是因为执行了remove()后不可以马上再执行remove(),因为原来lastRet指向的元素已经被删除,马上再删也不知道该删哪个元素了。
  • 换句话说,先执行next()/previous()后,再执行完remove()后lastRet指向的元素就会失效了,所以需要lastRet置为-1。
    Java源码分析 AbstractList的迭代器实现(Itr和ListItr)_第4张图片
    Java源码分析 AbstractList的迭代器实现(Itr和ListItr)_第5张图片
  • 之所以add()方法中要把lastRet置为-1,是因为如果执行顺序是previous(); add();,且假如add()方法中没有把lastRet置为-1,那么此时lastRet指向的元素是错误的。如下图所示,lastRet如果不动弹的话,那么将会指向新增加元素(绿色节点),而这样肯定不符合lastRet的含义了。
    Java源码分析 AbstractList的迭代器实现(Itr和ListItr)_第6张图片
  • set(E e)方法的执行的前提就是,lastRet处于有效状态,即lastRet指向的元素是有效的,不是-1。
  • next()/previous()方法能够使得lastRet恢复有效状态,而remove()/add()会使得lastRet变成无效状态,remove()/set()的执行前提是lastRet处于有效状态。

你可能感兴趣的:(Java)