分析ArrayList在遍历时修改报错的原因

以前使用for(:){}遍历List集合并同时修改List集合中的内容时会报:ConcurrentModificationException错误,这个错误就是提示我们:方法中有对象的并发修改,但不允许这种修改时,所以抛出此异常。

1.模拟遍历集合的同时修改集合(一)

新建一个list集合,并给该集合依次添加1-5的字符窜,然后遍历集合,在遍历集合的时候删除”2”

        public static void main(String[] args) {
             List<String> list = new ArrayList<String>();
                list.add("1");
                list.add("2");
                list.add("3");
                list.add("4");
                list.add("5");
                System.out.println("原来的list:" + list);
                for (String string : list) {
                    System.out.println(string);
                    //如果获取的内容是"2",就把它删除
                    if ("2".equals(string)) {
                        list.remove(string);
                    }
                }
                System.out.println("修改后的list:: " + list);
        }

输出的结果:

    原来的list:[1, 2, 3, 4, 5]
    1
    2
    Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
        at java.util.ArrayList$Itr.next(Unknown Source)
        at com.test.jh.TestListDemo.main(TestListDemo.java:16)

当集合遍历到”2”这个元素的时候,就出现了ConcurrentModificationException异常,拿到底是哪一个方法抛出的异常?其实是Iterator类中next()的方法抛出的异常。

2.foreach遍历集合的原理分析

通过反编译技术,反编译上面mian方法中的代码

    public static void main(String args[])
    {
        List list = new ArrayList();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        //1.字符串通过+拼接的底层实现也是通过StringBuilder这个类
        System.out.println((new StringBuilder("原来的list:")).append(list).toString());
        //2.使用for (String string : list) 遍历集合的底层是使用Iterators实现
        for (Iterator iterator = list.iterator(); iterator.hasNext();)
        {
            //3.重点:这里调用了next()方法,其实上面的那个错误就是调用了这个方法报错的
            String string = (String)iterator.next();
            System.out.println(string);
            if ("2".equals(string))
                list.remove(string);
        }

        System.out.println((new StringBuilder("修改后的list:: ")).append(list).toString());
    }

同过分析foreach遍历集合的原理,遍历集合的底层是使用Iterators实现,并且报错是因为执行了String string = (String)iterator.next();这一行代码

3.ArrayList的源码分析

查看Iterator中的next()方法

因为报错的原因是执行了:String string = (String)iterator.next();这句,那查看Iterator类的代码

        //把集合修改的次数(modCount)赋值给expectedModCount记录下来
        int expectedModCount = modCount;    

        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];
        }


        //主要用来判断集合的修改次数是否合法
        final void checkForComodification() {
            //modCount代表集合修改的次数(例如:每list.add()一次就加1,list.remove()一次也加1)
            //expectedModCount的值是等于开始遍历集合时的修改的次数
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

checkForComodification()方法中的modCount与expectedModCount的值不相等就会抛出异常。那么他们两个值分别在哪里被修改了呢?其实modCount的值是在list集合执行add,remove等操作的时候赋值,而expectedModCount是在new Iterator()时,把modCount的值赋给了expectedModCount记录。

查看ArrayList类的源码

那查看ArrayList类的remove()代码,发现remove方法中真的有对modCount++

    public E remove(int index) {
        rangeCheck(index);
        //记录了集合修改的次数,其实集合中的add()方法中也有modCount++;的代码
        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

聪明的你,从上面的代码就可以知道,当我们遍历集合的并修改集合的内容时,为什么会导致checkForComodification()中的值不相等就会抛出ConcurrentModificationException异常。

4.遍历List集合并同时修改List集合中的内容报错的原因

    public static void main(String args[])
    {
        List list = new ArrayList();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");

        System.out.println((new StringBuilder("原来的list:")).append(list).toString());
        //3.重点:list.iterator()
        for (Iterator iterator = list.iterator(); iterator.hasNext();)
        {
            String string = (String)iterator.next();
            System.out.println(string);
            if ("2".equals(string))
                list.remove(string);
        }

        System.out.println((new StringBuilder("修改后的list:: ")).append(list).toString());
    }

1.当代码在遍历的时候执行到list.iterator()时候就创建了一个Iterator对象,并在创建这个Iterator对象的时候modCount的值赋值给了expectedModCount,这时expectedModCount的值就是集合当前修改的次数。

     //执行list.iterator()调用了这个方法
     public Iterator<E> iterator() {
        return new Itr();
     }

     private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        //创建这个Iterator对象的时候modCount的值赋值给了expectedModCount,这时expectedModCount的值就是集合当前修改的次数
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        .....
    }

2.当代码在执行到下面的这个语句的时候,执行了list.remove(),这个时候modCount++;导致了modCount与expectedModCount不相等。那些下一次的循环在调用String string = (String)iterator.next();的时候就会导致next()方法中的checkForComodification()方法中的modCount与expectedModCount的值不相等就会抛出异常ConcurrentModificationException。

    if ("2".equals(string))
                list.remove(string);

  • 总结

    其实出现异常就是我们在遍历集合的时候修改了集合的内容,导致modCount的值在增加,而expectedModCount得值没有增加,所以在调用next()方法中的checkForComodification()方法判断集合的修改次数是否合法就会报错。

6.模拟遍历集合的同时修改集合(二)

新建一个list集合,并给该集合依次添加1-5的字符窜,然后遍历集合,在遍历集合的时候删除”4”(倒数第二个)

        public static void main(String[] args) {
             List<String> list = new ArrayList<String>();
                list.add("1");
                list.add("2");
                list.add("3");
                list.add("4");
                list.add("5");
                System.out.println("原来的list:" + list);
                for (String string : list) {
                    System.out.println(string);
                    //重点:是删除"4",而不是"2",那么会报错吗?其实不会
                    if ("4".equals(string)) {
                        list.remove(string);
                    }
                }
                System.out.println("修改后的list:: " + list);
        }

输出的结果是:

    原来的list:[1, 2, 3, 4, 5]
    1
    2
    3
    4
    修改后的list:: [1, 2, 3, 5]

从上面的可以看出,靠,居然没有报错成功删除了?这是什么情况?

分析原因

1.当执行到下面的代码的时候,list.remove()删除了一个元素,导致size也减少了1。

    //当前的cursor=4,size=5
    if ("4".equals(string)) {
         list.remove(string);//删除了一个size-1等于4
    }

2.接着当增强for循环再次判断hasNext():

    public boolean hasNext() {
            //当遍历到"4"的时候,当前的cursor=4,当前的size=4
            return cursor != size;
    }

3.当执行到for (Iterator iterator = list.iterator(); iterator.hasNext();)的iterator.hasNext()时,返回的值是false。为什么呢?当遍历到”4”的时候,当前的cursor=4,size=4,导致hasNext()返回的值是false导致直接退出了for循环,漏掉了第五次循环(第五次也是最后一个循环),导致没有执行String string = (String)iterator.next();就没有报错。

4.如果我们删除的那个不是”4”,而是”3”,结果导致当前的cursor=3,当前的size=4,当下一次执行iterator.hasNext()判断时,返回的结果是true,导致String string = (String)iterator.next();会执行,删除一个元素后,导致modCount的值在增加,而expectedModCount得值没有增加,所以在调用next()方法中的checkForComodification()方法判断集合的修改次数是否合法就会报错。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值