Java循环陷阱:在for-each中删除集合数据导致的异常原理

3476 篇文章 105 订阅

当我们在遍历 Java 集合(例如 ArrayList)时,如果在遍历过程中尝试修改集合(例如添加或删除元素),就会遇到 ConcurrentModificationException 异常。大家可能会对这个异常感到困惑,因此在这篇文章中,我将详细解释这个异常的产生原理,以及如何正确地在遍历集合的同时修改它。

ConcurrentModificationException 的产生原理

在 Java 集合类中,有一个 modCount 字段,这个字段记录了集合的“结构修改次数”。每当我们添加或删除集合元素时,modCount 就会增加。

这是集合中add的源码

js复制代码  * @param e element to be appended to this list
         * @return <tt>true</tt> (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }

可以看到当add方法调用时modCount++

当我们使用 for-each 循环或者 Iterator 遍历集合时,会创建一个迭代器对象,这个迭代器对象有一个 expectedModCount 字段,其值在创建时被赋值成 modCount来保持一致。

在遍历的时候,迭代器会在每次开始时检查 expectedModCount 是否等于 modCount。如果这两个值不等,迭代器就会抛出 ConcurrentModificationException 异常。

下面是一个简单的代码示例,演示了在 for-each 循环中尝试修改 ArrayList 时会抛出 ConcurrentModificationException 异常:

js复制代码    public static void main(String[] args) {
            List<String> arrayList = new ArrayList<String>();
            arrayList.add("1");
            arrayList.add("2");//modCount 为 2
            Iterator<String> iterator = arrayList.iterator();
            while(iterator.hasNext()) {
                String list = iterator.next();
                if("2".equals(list)){
                    arrayList.remove(list);//因为调用方是arrayList所以modCount会加1,但是**expectedModCount 还是2
                }
            }
            System.out.println("arrayList:"+arrayList);
        }

在这个代码中,在arrayList中调用了2次add方法所以modCount等于2。 循环开始时创建了一个迭代器,这时 expectedModCount 被设置为 modCount 的当前值,也就是 2。

然后在遍历过程中,我们尝试删除 "2",但是调用方是arrayList这导致 modCount 增加到 3,但 expectedModCount 的值仍然是 2,因此在下一次迭代开始时,迭代器抛出了 ConcurrentModificationException 异常。

大家可能会好奇,不是只有俩个值吗!为甚么会在循环一次?

这与 Java 的 ArrayList 的迭代器实现有关。为了解释这个问题,我们需要深入了解 ArrayListIterator 是如何工作的。

ArrayListIterator 在每次调用 next() 方法时,会检查是否还有更多元素。这是通过比较一个 cursor 的值(该值表示迭代器当前所在的位置)和 ArrayList 的大小来实现的。如果 cursor 不等于 ArrayList 的大小,那么 hasNext() 方法就返回 true

第一次循环进来 cursor=0,size=2,进行下一次循环,

cursor=1,size=2 ,进行下一次循环,注意:在刚才的代码中,在遍历到第二个元素时删除了一个数据

所以第三次判断 cursor=2,size=1 仍然为true 继续向下执行

迭代器会检查 modCount 是否等于 expectedModCount。此时 modCount 已经增加到3,而 expectedModCount 仍然是2,因此迭代器会抛出 ConcurrentModificationException 异常。

正确的修改方法

那么,我们应该如何在遍历集合的过程中正确地修改它呢?其实,Iterator 为我们提供了 remove 方法,我们可以通过这个方法来删除当前元素。这个方法在删除元素后,会正确地更新 expectedModCount 的值,使其等于 modCount,从而防止 ConcurrentModificationException 异常的产生。

以下是一个使用 Iteratorremove 方法在遍历过程中删除元素的代码示例:

vbnet复制代码javaCopy code
List<String> arrayList = new ArrayList<String>();
arrayList.add("1");
arrayList.add("2");
Iterator<String> iterator = arrayList.iterator();
while(iterator.hasNext()) {
    String list = iterator.next();
    if("2".equals(list)){
        iterator.remove();
    }
}

这段代码中,我们使用 iterator.remove(); 来删除元素,而不是 arrayList.remove(list); 。这样,迭代器就能正确地跟踪 modCount 的变化,从而避免 ConcurrentModificationException 异常。

总结起来,如果我们想在遍历集合的过程中修改集合,应该使用 Iteratorremove 方法。如果我们需要在遍历过程中添加元素,应该选择其他方法,例如先收集需要添加的元素,然后在遍历结束后添加到集合中。 实际开发中可以用removeIf方法来做删除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值