震惊!遍历集合时直接调用remove()可能会出现问题!

csharp 复制代码
我们在使用Java集合时,经常会遇到这样的代码:

    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");

    for (String s : list) {
        if (s.equals("B")) {
            list.remove(s);  // 直接调用remove
        }
    }

按照Java规范,这段代码应该抛出ConcurrentModificationException,但实际运行时,有时却不会抛出异常。这是为什么呢?

底层机制分析

1. modCount机制

ArrayList内部维护了一个modCount变量,记录结构性修改次数:

protected transient int modCount = 0; // 修改计数器

每次执行add()remove()等操作时,modCount都会递增。

2. Iterator的检查机制

增强for循环实际上使用了Iterator,其内部会记录初始的modCount

int expectedModCount = modCount; // 创建Iterator时记录

每次调用next()方法时,会检查:

java 复制代码
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

为什么有时不抛出异常?

关键点在于:Iterator只在调用next()时检查modCount

特殊情况分析

假设list初始为["A", "B", "C"]:

  1. 删除第一个元素"A":

    • 第一次循环删除"A"(modCount++)
    • 第二次循环调用next()时发现modCount变化 → 抛出异常
  2. 删除倒数第二个元素"B":

    • 第一次循环删除"B"(modCount++)
    • 第二次循环时hasNext()发现已到末尾 → 不调用next() → 不检查modCount → 不抛异常

正确做法

1. 使用Iterator.remove()

java 复制代码
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("B")) {
        it.remove();  // 正确方式
    }
}

2. 使用removeIf()(Java8+)

list.removeIf(s -> s.equals("B"));

3. 创建新列表暂存待删元素(通用方法)

scss 复制代码
List<String> toRemove = new ArrayList<>();
for (String s : list) {
    if (s.equals("B")) {
        toRemove.add(s);
    }
}
list.removeAll(toRemove);  // 最后统一删除

4. 使用CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>(); // 可以安全地在遍历时修改

方法对比

方式 安全性 适用场景 性能 代码复杂度
直接remove() - 简单但不安全
Iterator.remove() 单线程 O(n) 中等
removeIf() Java8+ O(n) 最简单
暂存后删除 所有版本 O(2n) 较复杂
CopyOnWriteArrayList 多线程 O(n)写操作较慢 简单
相关推荐
舒一笑6 分钟前
我的开源项目-PandaCoder迎来史诗级大更新啦
后端·程序员·intellij idea
@昵称不存在1 小时前
Flask input 和datalist结合
后端·python·flask
zhuyasen2 小时前
Go 分布式任务和定时任务太难?sasynq 让异步任务从未如此简单
后端·go
东林牧之2 小时前
Django+celery异步:拿来即用,可移植性高
后端·python·django
超浪的晨3 小时前
Java UDP 通信详解:从基础到实战,彻底掌握无连接网络编程
java·开发语言·后端·学习·个人开发
AntBlack3 小时前
从小不学好 ,影刀 + ddddocr 实现图片验证码认证自动化
后端·python·计算机视觉
Pomelo_刘金3 小时前
Clean Architecture 整洁架构:借一只闹钟讲明白「整洁架构」的来龙去脉
后端·架构·rust
双力臂4043 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
midsummer_woo5 小时前
基于spring boot的医院挂号就诊系统(源码+论文)
java·spring boot·后端
Olrookie6 小时前
若依前后端分离版学习笔记(三)——表结构介绍
笔记·后端·mysql