(注意:本文源码基于JDK1.8)
ArrayList是基于数组的容器类,赶紧学学修改元素的方法,ArrayList本身实现的set()方法可以修改指定下标处的元素,而另一个同名的set()方法则是ListItr迭代器类中实现的,它只能修改已经遍历后的最后一个元素!一起学习一下修改元素是怎么做到的?
ArrayList中的set()方法
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
用于修改元素的set()方法,第一个参数index表示要替换元素的索引(下标),第二个参数element表示修改后的元素对象
1、首先检查传入下标index是否合法
这份检查工作由rangeCheck()方法完成,rangeCheck()方法接受传入的下标index,当index值不符合范围时(小于0或者大于等于size值),则会在rangeCheck()方法中抛出IndexOutOfBoundsException对象
2、定义局部变量保存指定下标处的旧元素
从ArrayList对象持有的Object[]类的数组对象elementData中,获取传入的指定下标index取出的旧元素,然后赋值到一个局部变量oldValue
3、修改指定下标处的元素
将ArrayList对象持有的elementData数组对象的指定下标index处,指向修改后传入的元素对象element
4、返回旧元素
return语句则返回下标index处过去保存的旧元素对象
rangeCheck()方法分析
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
用于检查下标范围是否合法的方法,传入的参数index表示位于数组中的下标
你传入的下标值必须小于ArrayList对象持有的size值,Array对象持有的size即表示当前的元素总数,也表示元素即将插入的位置,比如size的初值是0,即表示ArrayList持有的元素为0,也表示下一个元素即将插入到下标0的位置。如果传入的index下标不合法,调用方将收到一个IndexOutBoundsException异常对象!
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();
}
}
定义在ListItr类中的set()方法,ListIt类是个迭代器类,定义在ArrayList类的内部,我们可以通过调用ArrayList的listIterator()系列方法得到一个ListItr对象,每个ListItr对象表示一个迭代器对象!
这个ListItr类的set()方法很有特点,它只会替换最后一次遍历后的元素,且并不会返回被替换的旧元素!
1、检查最后一次遍历的元素的下标
通过判断lastRet的值即可得知ArrayList是否发生过遍历行为,lastRet的初始值是-1,每当你遍历一个ArrayList中的元素,这个lastRet的值会加1,比如你只访问过第一个元素,此时lastRet的值是0。当lastRet小于0,则说明我们没有使用迭代器遍历过任何元素,此时调用set()方法则会抛出一个IllegalStateException对象。
2、检查是否发生过并发修改,防止多线程使用ArrayList
调用checkForComodification()方法实现并发修改的检查,ArrayList并不支持多线程下使用,无法保证共享变量elementData的元素操作,所以就有了并发检查的方法。通过两个变量,modCount、expectedModCount进行相等对比!modCount是ArrayList对象持有的实例变量、而expectedModCount则是Itr对象持有的实例变量(注:Itr是ListItr的父类),expectedModCount的初始值是在ListItr对象创建的时候赋值的,这个值就是modCount值,如果modCount值发生过改变,那么一定与expectedModCount值为不同(modCount值在哪些地方会被改变?我数了下大概后8处位置修改了modCount的值,基本上元素发生变化,modCount的值就会发生改变)。modCount与expectedModCount不相等,就会抛出ConcurrentModificationException异常对象了
3、尝试替换元素,严格防范多线程使用ArrayList
在try代码块中,实际使用的是ArrayList的set()方法进行修改元素,如果修改元素失败(因为下标不符合要求),先捕获下标越界的IndexOutOfBoundsException异常对象,然后再新抛出一个ConcurrentModificationException异常对象!这样很有道理,毕竟传入要修改的元素下标是lastRet的值,lastRet的值明明指向的是一个最后访问过的元素,如果发生IndexOutBoundsException对象,那说明其他线程修改了元素数量了,不然是不可能数组越界的!所以作者自然在捕获到IndexOutOfBoundsException对象后再新抛出一个ConcurrentModificationException异常对象!
checkForModification()方法分析
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
用于fail-fast机制的checkForComodification()方法,防止多线程下使用ArrayList!
总结
无论看到IndexOutOfBoundsException对象、还是ConcurrentModificationException对象,只要对于ArrayList源码理解的不错,真的随便hold住这俩异常,看来学习源码,真的是进步很快,加油呀!