简单说明:ArrayList 在 For 循环中进行删除而产生异常的原因

简介: 经常会有人这么对 list 进行遍历,错而不自知。示例代码如下:public static void main(String[] args) { List<String> list = new ArrayList<>(); list.

经常会有人这么对 list 进行遍历,错而不自知。

示例代码如下:

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    list.add("ddd");

    for (String str : list) {
        if ("aaa".equals(str)) {
            list.remove("aaa");
        }
    }
}

以上代码执行导致的报错信息如下:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at demo.service.impl.test.main(test.java:14)

网上有很多博客对此都做了说明,这篇文章通过比较浅显易懂的方式说明报错产生的原因。

一、list.add

list.add 代码执行时,有一个变量发生改变了,那就是 modCount。在代码中 list.add 共执行4次,所以 modCount 的值为 4。

注:listadd()remove()clear() 都会改变 modCount 值。

二、for (String str : list)

for (String str : list) 调用的是 ArrayList 中内部类 ItrItr 是对 Iterator 的实现。而在 Iterator 开始前,会先执行 int expectedModCount = modCount

此时 expectedModCountmodCount 均为 4

三、list.remove("aaa")

在此处先看一下会报错的原因,以下是源码:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

modCountexpectedModCount 不相等了,所以报错。

有人可能会跟我有一样的想法,为什么 list.remove("aaa") 时,不把 expectedModCount = modCount 重新赋值一次。其实是有的,只是调用的方法错了。

例子中 list.remove("aaa") 调用的 remove 源码如下:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                // 示例中调用的是此处的 fastRemove
                fastRemove(index);
                return true;
            }
    }
    return false;
}

而使 modCount 的值改变的是其中的 fastRemove 方法。

fastRemove 源码如下:

private void fastRemove(int index) {
    // 此处 modCount + 1
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

而真正使 expectedModCount = modCount 执行的源码如下:

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        AbstractList.this.remove(lastRet);
        if (lastRet < cursor)
            cursor--;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
    }
}

此代码在内部类 Itr 中。

这也就是为什么会说,如果 list 在循环中有删除操作,最好用 iterator 迭代的方式去做。

四、总结

简单总结一下

  • list.remove() 没有对 expectedModCount 重新赋值
  • iterator.remove()expectedModCount 重新赋值

建议大家跟踪一下源代码,代码量不多,也很容易理解。

附录:内部类 Itr 源码(不长,分分钟看完)

private class Itr implements Iterator<E> {
    /**
     * Index of element to be returned by subsequent call to next.
     */
    int cursor = 0;

    /**
     * Index of element returned by most recent call to next or
     * previous.  Reset to -1 if this element is deleted by a call
     * to remove.
     */
    int lastRet = -1;

    /**
     * The modCount value that the iterator believes that the backing
     * List should have.  If this expectation is violated, the iterator
     * has detected concurrent modification.
     */
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size();
    }

    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
相关文章
|
消息中间件 Java Kafka
spring boot 整合kafka
spring boot 整合kafka
505 8
|
JSON Java API
Spring Boot 整合 springdoc-openapi
Spring Boot 整合 springdoc-openapi
|
消息中间件 Java Kafka
SpringBoot接入Kafka
相信大家在遇到洪峰流量的时候都是考虑过消息队列这个东西的,市面上有很多主流的消息队列模型,今天呢,我就使用SpringBoot项目简单接入一下主流的Kafka消息队列。
1831 0
|
Java
ArrayList与LinkedList的遍历删除元素方法
ArrayList与LinkedList的遍历删除元素方法
721 0
|
5天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1483 0
|
5天前
|
人工智能 自然语言处理 安全
阿里云AI数智鉴密:AI 生成内容如何拿到一张"防篡改的身份证"
隐形水印 + C2PA签名:让AI生成内容“持证上岗”。
1131 0
|
14天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
3779 4
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考