Java之ArrayList源码分析(第九篇:ListItr迭代器分析)

(注意:本文基于JDK1.8) 

ArrayList中重写了基类AbstractList的两个listIterator()方法,这两个方法的返回值都是ListItr对象,ListItr类产生的对象是作为增强型的迭代器而设计的,接下来我们一探究竟……

 

ListItr的类结构

    private class ListItr extends Itr implements ListIterator<E> {
       …………省略很多代码………… 
}

ListItr类定义在ArrayList的内部,作为ArrayList的普通内部类,ListItr继承类基础的顺序迭代器类Itr(详细介绍),并实现了ListIterator接口,我们先去看看ListIterator接口对外提供了哪些接口,这些接口表示作为增强型迭代器应该具备的能力!

 

ListIterator接口介绍

public interface ListIterator<E> extends Iterator<E> {
   …………省略很多代码…………
}

ListIterator接口定义了作为增强型线性表迭代器的功能,ListIterator接口继承了普通迭代器Iterator接口的功能,看下ListIterator接口中定义了哪些方法

根据上文截图,在Iterator接口的基础功能之上,ListIterator接口又新增了add、hasPrevious、nextIndex、previous、previousIndex、set方法

 

ListItr对象的创建

        ListItr(int index) {
            super();
            cursor = index;
        }

ListItr类一个参数的构造方法,传入的index值代表线性表的下标值,在构造方法的内部首先调用了父类的构造方法,接着将传入的参数index赋值给ListItr对象持有的实例变量cursor保存起来(表示从哪个元素开始遍历),实例变量cursor定义在父类Itr中的,表示一个遍历元素时的游标值,它的默认值是0。这就是ListItr对象的创建

 

父类Itr的构造方法

        Itr() {}

父类Itr的构造方法没有做任何初始化的工作(注意:Itr类中定义的实例变量,在Itr类加载到JVM时已经完成了初始化)

 

ListItr中的hasPrevious()方法分析

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

hasPrevious()方法用于判断当前ArrayList在遍历过程中是否存在上一个元素,当ListItr对象持有的实例变量cursor不等于0时,我们认为当前ArrayList中仍然存在上一个元素没有遍历过,当cursor等于0时,已经是最左侧的元素,或者说是第一个元素,当然不会有上一个元素的存在

 

ListItr中的nextIndex()方法分析

        public int nextIndex() {
            return cursor;
        }

nextIndex()方法返回当前迭代器遍历当前元素后的下标cursor,在迭代器中,cursor的初始值是0,第一次调用Itr类的next()方法时,返回的是cursor值为0的元素

 

ListItr中的previousIndex()方法分析

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

previousIndex()方法返回迭代器即将要遍历元素的上一个元素,在实现上直接使用cursor-1即可得到上一个元素,因为cursor值代表的是迭代器调用next()返回的元素下标

 

ListItr中的previousIndex()方法分析

        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

previous()方法是ListItr迭代器对象用来返回上一个元素对象的方法

1、fail-fast机制,防止多线程下遍历ArrayList

首先调用了checkForComodification()方法,用于检查ArrayList持有的元素数量是否发生过改变

2、游标值先回退1个位置

获取ListItr对象持有的cursor值,接着将cursor值减去1,临时交给局部变量i保存,目前cursor值是稍后用于返回的元素下标

3、检查游标值

如果i的值小于0,说明已经没有上一个元素,此时会抛出一个NoSuchElementExcepton对象

4、获取ArrayList对象持有的底层数组对象,并保存到局部变量中

获取当前ArrayList对象持有的数组对象elementData并赋值给局部变量elementData保存

5、再次检查cursor值

再次对即将遍历的局部变量i与数组对象elementData的长度进行对比,如果即将遍历的元素下标i大于等于数组elementData的长度,说明在遍历过程中,ArrayList被其他线程修改了元素数量,此时抛出ConcurrentModificationException,告知用于不要在多线程下使用ArrayList

6、更新游标值

先更新当前遍历元素的下标cursor为局部变量i的值

7、更新lastRet值

先把返回的元素下标记录在ListItr对象持有的实例变量lastRet中(lastRet定义在父类Itr中)

8、返回底层数组对象中持有的元素

从ArrayList对象持有的底层数组对象elementData中,取出下标lastRet对应的元素对象返回调用者,以表示上一个元素

 

父类Itr中的checkForComodification()方法

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

checkForComodification方法的功能是fail-fast机制的检查者,它检查当前线性表在遍历过程中,是否发生过变更元素的行为(添加、删除、清除),每次元素改变,ArrayList对象持有的modCount都会+1的操作(注意:modCount定义在ArrayList的父类AbstractList中),而expectedModCount则是ListItr对象持有的实例遍历,它在ListItr对象创建时,存储了ArrayList最初的modCount值,通过对比ArrayList对象持有的modCount与ListItr对象持有的expectedModCount值,如果不相等,则会直接抛出ConcurrentModificationException对象!

 

ListItr中实现的set()方法分析

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

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

set()方法用于改变最后一次通过next()或者previous()方法返回的元素对象,或者说它可以变更lastRet下标处的元素对象

1、首先检查lastRet下标值

lastRet的值不能小于0,否则就抛出IllegalStateException对象

2、fail-fast机制检查

调用checkForComodification()方法

3、尝试修改元素

在try ..catch...代码块中,实际是通过ArrayList对象的set()方法进行修改元素,set()方法接受lastRet、以及传入的元素对象e,set()方法可能会抛出IndexOutOfBoundsException对象,catch代码块中捕获该异常,然后再次抛出一个ConcurrentModificationException对象(因为只有在其他线程中将ArrayList中的元素数量改变了,才可能出现IndexOutOfBoundsException的问题,所以抛出ConcurrentModificationException)

 

 

ListItr实现的add()方法

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

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

add()方法用于添加一个元素到当前ArrayList对象中,它接受一个参数类型为E的对象

1、fail-fast机制检查

首先调用checkForComodification方法(见本文8号知识点)做一次检查

2、尝试添加元素

检查通过后,则是try...catch...代码块的执行,先将ListItr对象持有的cursor赋值给局部变量i,真正添加元素的工作是通过ArrayList的add()方法完成的,add()方法接受下标值i、以及元素对象e

3、更新游标值cursor

元素添加完成后,原游标值加1,并赋值给ListItr对象持有的cursor

4、更新lastRet值为初始值

将ListItr记录的最后遍历返回的元素下标lastRet更新为初始值-1,说明迭代器上一次并没有进行遍历行为,而是其他行为如添加元素、删除元素、更新元素

5、fail-fast机制更新

ArrayList添加元素后,ArrayList对象持有的modCount已经增加了1,此时就需要将ListItr对象持有expectedModCount重新赋值一次

6、针对异常情况,告知用户真是原因为多线程下使用ArrayList

ArrayList的add方法可能会抛出IndexOutBoundsException,此处做了捕获处理,捕获之后再次抛出ConcurrentModificationException对象,因为add方法抛出IndexOutOfBoundsException的根本原因是ArrayList的元素数量在某一刻改变了(进一步说明ArrayList无法在多线程环境下使用)

 

总结

1、ListItr迭代器比Itr迭代器新增加了向前遍历元素的能力,通过hasPrevious()、previousIndex()、previous()方法来实现的

2、ListItr迭代器比Itr迭代器新增加了添加元素的能力,add()方法实现了该功能

3、ListItr迭代器比Itr迭代器新增加了修改元素的能力,set()方法实现了该功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值