震惊!遍历集合时直接调用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)写操作较慢 简单
相关推荐
稚辉君.MCA_P8_Java13 分钟前
Gemini永久会员 Java中的四边形不等式优化
java·后端·算法
稚辉君.MCA_P8_Java24 分钟前
通义 插入排序(Insertion Sort)
数据结构·后端·算法·架构·排序算法
q***697734 分钟前
【Spring Boot】统一数据返回
java·spring boot·后端
v***598335 分钟前
DeepSeek API 调用 - Spring Boot 实现
windows·spring boot·后端
Hollis Chuang36 分钟前
Spring Boot 4.0 正式发布,人麻了。。。
java·spring boot·后端·spring
Moshow郑锴1 小时前
实战分享:用 SpringBoot-API-Scheduler 构建 API 监控闭环 —— 从断言验证到智能警报
java·spring boot·后端·任务调度
金融数据出海1 小时前
日本股票市场渲染 KlineCharts K 线图
前端·后端
1***t8272 小时前
将 vue3 项目打包后部署在 springboot 项目运行
java·spring boot·后端
疯狂的程序猴2 小时前
iOS 日志管理的工程化实践 构建从开发调试到系统日志分析的多工具协同体系
后端
申阳2 小时前
Day 17:03. 基于 Tauri 2.0 开发后台管理系统-登录页面开发
前端·后端·程序员