ConcurrentModificationException究极源码分析,为什么不能在迭代器遍历中,一边遍历,一边删除?以及解决方案

问题

我们都知道在使用迭代器遍历集合时,删除集合中的元素会报java.util.ConcurrentModificationException异常,那么这是为什么呢?
先来看看问题代码:

List<String> list = new ArrayList<>();
list.add("古力娜扎");
list.add("李沁");
list.add("琪琪");
System.out.println(list);
for (String s : list) {
    if(s.equals("古力娜扎")){
        list.remove(s);
    }
}
System.out.println(list);

在这里插入图片描述
可以看到异常信息是在ArrayList的内部类迭代器类Iter中的checkForComodification()方法中被抛出的,并且这个方法在next()方法中被调用,那我们就进去看看

分析源码

在ArrayList中根据自身结构信息封装了一个迭代器Iter,其中的核心代码:

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
        int expectedModCount = modCount;

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

        @SuppressWarnings("unchecked")
        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];
        }
		......
	}

找到了checkForComodification方法,我们进去揭开它的秘密!

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

好像也没有什么神秘的嘛
就是看看modCountexpectedModCount的值是否相等,如果不相等,就抛出并发修改异常。
当我们在遍历的过程中调用remove(Object o)方法的时候,其实就修改了modCount的值,导致modCountexpectedModCount不相等,就触发java.util.ConcurrentModificationException了呀。
在这里插入图片描述
在这里插入图片描述
这就完了吗?当然还没有,这个modCount是什么意思呢?
我们也可以在jdk中找到该字段的解释,大致的解释一下:

  • 该字段被用于记录集合结构的修改的次数,包括对集合长度的修改
  • 结构的改变可能导致遍历过程中产生不正确的结果,这个字段是jdk提供的保证在遍历的时候为了避免不确定性而采取的快速失败的措施
  • 该字段可以被迭代器和迭代器的实现类选择使用,如果不希望实现提供块数失败的迭代器,此字段可以忽略

到此为止,大家应该对java.util.ConcurrentModificationException产生的原因很清晰了吧!

那么如何在一边遍历,一边删除元素呢?

还是可以做到了,下面提供一些方式
1.使用for循环
正向循环遍历,重点是要保证角标一致

for(int i = 0;i < list.size();i++){
    String string = list.get(i);
    if(string.equals("古力娜扎")){
        list.remove(i);
        //保证删除后的数组角标一致
        i = i - 1;
    }
}

逆向循环遍历

for(int i = list.size() - 1;i >= 0;i--){
    String string = list.get(i);
    if(string.equals("古力娜扎")){
        list.remove(i);
    }
}

2.使用迭代器提供的remove方法

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(4);
list.add(3);
System.out.println(list);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
    Integer integer = iterator.next();
    if(integer==2){
        iterator.remove();
    }
}
System.out.println(list);

3.jdk1.8后的removeif()方法

list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.equals("李沁");
    }
});
//lomda表达式
list.removeIf(arg -> arg.equals("琪琪"));

总结:

  • 不是在集合的遍历过程中不能删除元素,而是你的方法不对导致的导致modCountexpectedModCount不相等,从而触发java.util.ConcurrentModificationException
  • 在集合的遍历过程中,修改集合的结构确实容易产生不被预期的结果,因此我们尽量避免这样的操作,或者通过特别的手段保证他的结果不会出乎预期
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值