You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ArrayList使用foreach删除元素时的ConcurrentModificationException异常差异问题

为什么ArrayList大小为2时foreach删除不抛异常,大于2时会抛?

这个问题得从ArrayList的foreach循环底层实现和它的快速失败(fail-fast)机制说起,我给你一步步拆解:

首先搞懂foreach循环的本质

Java里用foreach遍历集合时,底层其实是依赖Iterator迭代器来工作的。ArrayList内部的迭代器是一个叫Itr的内部类,它有个关键的校验逻辑:每次调用next()获取下一个元素前,都会对比集合的modCount(记录集合被修改次数的变量)和迭代器自己的expectedModCount(迭代器创建时保存的modCount值)。如果这两个值不相等,就会直接抛出ConcurrentModificationException——这就是所谓的快速失败,用来防止单线程或多线程环境下的并发修改导致遍历异常。

大小为2时的特殊情况

拿你给的例子来说:

ArrayList<String> a= new ArrayList<String>(Arrays.asList("abc","xyz")); 
for(String i : a){ 
    a.remove(i); 
}
  1. 迭代器刚创建时,cursor(当前遍历的位置指针)是0,expectedModCount等于集合初始的modCount(值为0)。
  2. 第一次循环:调用next()拿到"abc",cursor变成1;然后执行a.remove(i),这时候集合的modCount加1(变成1),集合大小从2变成1。
  3. 接下来判断是否还有下一个元素:迭代器的hasNext()方法是用cursor != size来判断的,此时cursor是1,size是1,所以返回false,循环直接结束。
  4. 重点来了:因为循环提前终止,根本不会再调用next()方法,也就不会触发modCount和expectedModCount的校验,所以自然不会抛出异常。最后集合里剩下"xyz",是因为删除第一个元素后,第二个元素的索引变成0,但循环已经停止了。

大小大于2时为什么会抛异常

假设我们把集合改成["a","b","c"],执行同样的删除逻辑:

  1. 第一次循环:拿到"a",cursor变成1;a.remove(i)后,modCount变成1,集合大小变成2。
  2. 判断hasNext():cursor是1,size是2,1 != 2返回true,所以要进入下一次循环。
  3. 调用next()获取下一个元素,这时候迭代器会先校验modCount(1)和expectedModCount(0),发现两者不相等,直接抛出ConcurrentModificationException。

额外补充:正确的删除姿势

如果想在遍历的时候删除元素,应该用迭代器自己的remove()方法,而不是直接调用集合的remove()。因为迭代器的remove()会同步更新expectedModCount,让它和modCount保持一致,比如:

Iterator<String> it = a.iterator();
while(it.hasNext()){
    String s = it.next();
    if(/* 你的删除条件 */){
        it.remove(); // 用迭代器的remove,不会触发异常
    }
}

内容的提问来源于stack exchange,提问作者moovon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:27:33