Java之ArrayList源码分析(第六篇:修改元素)

(注意:本文源码基于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住这俩异常,看来学习源码,真的是进步很快,加油呀!

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值