探究List中foreach循环里进行元素的remove操作引起的异常


关键词:

for
foreach
iterator.hasNext()
iterator.next()
iterator.remove()
list.remove
modCount
expectedModCount
快速失败机制(fail-fast)


一、问题引入

在这里插入图片描述

上图为阿里巴巴java开发手册的开发规范,foreach中为什么不能进行remove操作?反例中“1”删除不会报错,“2”删除出现ConcurrentModificationException异常?接下来,探究其原因

二、问题分析

要弄清这些问题,首先要了解集合中的remove方法和迭代器中的remove方法的区别(ArrayList、Iterator)

①ArrayList中的remove方法(如下图所示):

在这里插入图片描述
每次进入remove()方法,都会调用fastRemove(),然后modCount++;

②Iterator中的remove方法(如下图所示):

在这里插入图片描述
迭代器调用的其实是ArrayList中内部类Itr的remove方法,每次进入remove()方法,都会调用checkForComodification(),然后判断 if (modCount != expectedModCount),不相等就会抛出ConcurrentModificationException;

③modCount和expectedModCount

进一步就要搞清楚modCount和expectedModCount是什么,在哪赋值的
在这里插入图片描述
modCount是在抽象类AbstractList中定义的,所以对集合的操作都会改变modCount的值,和java中的快速失败机制有关(fail-fast)(一种错误检测机制,不需要通过复杂的算法)。
在这里插入图片描述
expectedModCount是在内部类Itr中定义的,初始化时会将modCount值赋给expectedModCount;
所以最终结论, foreach 循环里进行元素的remove操作所引发的异常,本质就是调用了checkForComodification()方法,判断modCount != expectedModCount为true时而抛出的ConcurrentModificationException(并发修改异常);

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

三、迭代器Iterator的执行原理

①foreach和Iterator

先分析foreach和Iterator之间的关系,打开编译后的.class文件(如下图):
在这里插入图片描述
编译后的.class文件
在这里插入图片描述
由图可以看出,foreach其实就是使用了Iterator来进行遍历的,出现ConcurrentModificationException原因就是在迭代器中使用了ArrayList中的remove方法,在迭代器外部修改了modCount的值,导致和expectedModCount值不相等抛出异常。

回到开发手册上的案例,为什么反例中“1”删除不会报错,“2”删除出现ConcurrentModificationException异常?

②Iterator中的方法,hasNext()、next()、remove()

先看迭代器中的三个方法hasNext()、next()、remove()
在这里插入图片描述

③迭代器Iterator中的执行原理

在这里插入图片描述
foreach循环的test01()改写为下图所示代码:

    @Test
    public void test01() {
        List<String> list = new ArrayList();
        list.add("1");
        list.add("2");
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            String item = (String)var2.next();
            if ("1".equals(item)) {
                list.remove(item);
            }
        }

        System.out.println(list); // 2
    }

在这里插入图片描述
cursor为下一个元素的索引,size为集合的大小,接着调用.next()方法,cursor变为1;
在这里插入图片描述
执行步骤分析:

	cursor=0;size=2
	cursor != size 为true,进入循环
	if条件为true,删除"1",
	由于是调用ArrayList中的remove方法,所以modCount++,即modCount - expectedModCount = 1
	此时,list.size() = 1,cursor = 1(调用迭代器中的next()方法,cursor+1)
	再次判断while中条件,为false,跳出循环,不会调用next(),自然也就不会调用checkForComodification();

foreach循环的test02()改写为下图所示代码:

    @Test
    public void test02() {
        List<String> list = new ArrayList();
        list.add("1");
        list.add("2");
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            String item = (String)var2.next();
            if ("2".equals(item)) {
                list.remove(item);
            }
        }

        System.out.println(list);
    }

执行步骤分析:

	cursor = 0;size = 2
	cursor != size 为true,进入循环
	调用next()后得到item = 1,cursor变为1
	if条件为false,进入第二次循环,此时,cursor = 1,size = 2;
	调用next()后得到item = 2,cursor变为2;
	如果此时调用的是Iterator中的remove,
	会有expectedModCount = modCount;
	调用ArrayList中的remove方法,所以modCount++,即modCount - expectedModCount = 1;
	并且此时list.size() = 1;
	再次进入while循环,
	list.size() = 1,cursor = 2,return cursor != size 为true,进入下一步,
	调用.next(),进入checkForComodification()方法中,
	modCount != expectedModCount为true,抛出异常ConcurrentModificationException;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值