震惊!遍历集合时直接调用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)写操作较慢 简单
相关推荐
llz_1123 小时前
web-第二次课后作业
前端·后端·web
红尘散仙8 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记10 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆10 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪10 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball61611 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_25183645711 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao11 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒12 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰13 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理