初学迭代器2, ArrayList的Iterator实现

学习的是ArrayList里面的迭代器.

JDK1.7 里面的源码如下:
private class Itr implements Iterator {

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

            }

        }

        final void checkForComodification() {

            if (modCount != expectedModCount)

                throw new ConcurrentModificationException();

        }

    }

cursor 这是一个游标, 用于判断当前迭代器指向容器中对象的索引, 简单点说cursor现在是指向第几个元素
lastRet 用来判断是不是正确调用remove
expectedModCount 这个我也不是很懂...现在感觉是用来判断是不是出现了并发修改用的

先看hasNext()这个方法, 感觉设计的人很聪明, 利用游标还有索引的结合就可以简单地构造了一个迭代器.
通过cursor和size来比较大小, 由于每次调用next()方法的时候,cursor都会自增1, 就是做指向了容器中下一个元素,
所以一旦cursor大于或者等于size就会抛异常, (因为cursor实际上指向的是对象的索引, 然后再获取该地址中的引用,这是我根据数组来思考得出来的, 欢迎指正)
保证了每一次迭代都会是容器里面存在的元素
//
next():
这里明确了迭代的过程中不允许对容器的长度进行并发修改. 因为本来容器中长度为10,
在迭代的过程中, 突然有一个线程来在容器中添加了一个元素, 这样就会导致遍历不对等不可预测的结果, 因此不允许并发修改.
通过了检测游标是否超出索引范围(就是大于或者等于size)后, 方法体里面首先会记住当前游标的值, 也就是索引值, 因为这个值是迭代器中当前迭代对象在容器中的索引值.接下来就是再次进行并发检测了, "ArrayList.this.elementData"这句代码是获取当前容器的全部对象,注意是每次迭代中都会重新获取一遍(所以中间如果发生了并发修改就会导致不可预测的结果).
由于在前面已经执行过了"索引与游标比较"和"并发检测", 所以按道理来说i是肯定不会超出索引, 所以一旦超出了, 就证明有一个线程对容器进行了操作, 导致i超出了索引了, 指向容器中一个不存在的元素.
接着就是获取当前迭代的对象(i), 然后cursor自增(游标指向了下一个元素), 注意这里是"elementData[lastRet = i]",是给lastRet给赋值了, 用于remove()方法.(原来API上说如果要调用remove, 就必须要先调用next方法, 就是这个原因)
//
​remove():
根据之前对next的解释, 如果没有调用next的话, lastRet就是等于-1, 所以抛出异常(非法状态异常).
在写解释的时候, 我突然想到了, 迭代器处于的当前线程是可以对容器进行操作的, 比如就像这个remove, 只是不能进行并发修改(这里欢迎指正哈)
每次移除都会移除迭代器最后返回的一个对象, 因为上面next里面说了lastRet是等于i的(而不是移除cursor指向的对象)
移除容器里面的元素之后, 被移除元素后面的元素都需要把索引往前面挪一下,所以要把迭代的位置重新调整,
将lastRet的值赋给了cursor(这里cursor指向的对象并没有发生改变, 只是这个对象在容器中的位置发生了改变而已).
再把-1赋值给lastRet(移除之前又要调用一次next咯....)

猜你喜欢

转载自cloudgan.iteye.com/blog/2344979
今日推荐