找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2703

积分

1

好友

371

主题
发表于 3 天前 | 查看: 15| 回复: 0

Java开发中,循环删除List元素是一项常见但容易出错的操作。即便是经验丰富的开发者,也可能不慎触发ConcurrentModificationException异常。本文将从一个典型错误案例入手,深入剖析异常根源,并详细介绍三种安全有效的实现方式。

程序员工作场景示意图

1. 新手常犯的错误

许多初学者首先会尝试使用foreach循环进行删除,代码如下:

public static void main(String[] args) {
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("CSDN");
    platformList.add("掘金");

    for (String platform : platformList) {
        if (platform.equals("博客园")) {
            platformList.remove(platform);
        }
    }

    System.out.println(platformList);
}

运行这段代码会立即抛出java.util.ConcurrentModificationException异常,即并发修改异常。

ConcurrentModificationException错误截图

为什么会出现这个异常?让我们先查看上述代码编译后的字节码:

foreach循环字节码示例

从字节码可以清晰地看到,foreach循环在底层是通过Iterator实现的,核心依赖于hasNext()next()方法。

接下来,我们查看ArrayListIterator的源码实现:

ArrayList Iterator实现源码

异常抛出的关键位置就在next()方法内部:

checkForComodification方法源码

next()方法中,第一行代码就调用了checkForComodification()。这个方法的核心是比较modCountexpectedModCount两个变量的值。在初始状态下,两者的值均为3。

但当执行platformList.remove(platform);语句后,modCount的值被修改为4。

ArrayList remove方法源码

因此,当第二次调用next()试图获取元素时,modCount(4)与expectedModCount(3)不再相等,从而触发异常。

modCount与expectedModCount不匹配示意图

既然foreach循环行不通,有哪些方法可以安全地实现循环删除呢?主要有以下三种途径:

  • 使用Iteratorremove()方法
  • 使用for循环正序遍历
  • 使用for循环倒序遍历

下面我们逐一进行详细解析。

2. 使用Iterator的remove()方法

直接调用Iterator自身的remove()方法是推荐的做法,实现代码如下:

public static void main(String[] args) {
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("CSDN");
    platformList.add("掘金");

    Iterator<String> iterator = platformList.iterator();
    while (iterator.hasNext()) {
        String platform = iterator.next();
        if (platform.equals("博客园")) {
            iterator.remove();
        }
    }

    System.out.println(platformList);
}

运行后输出结果为:

[CSDN, 掘金]

为什么iterator.remove()不会引发异常?关键在于其源码实现:

Iterator remove方法源码

每次成功删除一个元素后,该方法都会将当前的modCount值重新赋给expectedModCount,使得两者始终保持一致,从而顺利通过checkForComodification()检查。

3. 使用for循环正序遍历

通过传统的for循环和下标操作也能实现删除,但需要特别注意下标调整:

public static void main(String[] args) {
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("CSDN");
    platformList.add("掘金");

    for (int i = 0; i < platformList.size(); i++) {
        String item = platformList.get(i);
        if (item.equals("博客园")) {
            platformList.remove(i);
            i = i - 1;
        }
    }

    System.out.println(platformList);
}

这段代码有一个关键点:在删除元素后,需要执行i = i - 1;来修正下标。原因可以通过图示理解。

删除前,列表元素的下标分布如下:
列表元素初始下标示意图

当删除“博客园”(下标0)后,剩余元素会前移,下标变为:
删除元素后下标变化示意图

如果不将i减1,下一次循环时i的值为1,将直接访问“掘金”,导致“CSDN”被遗漏。因此,修正下标是确保每个元素都被检查的必要步骤。

4. 使用for循环倒序遍历

倒序遍历是另一种有效方式,它天然避免了下标修正的问题:

public static void main(String[] args) {
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("CSDN");
    platformList.add("掘金");

    for (int i = platformList.size() - 1; i >= 0; i--) {
        String item = platformList.get(i);
        if (item.equals("掘金")) {
            platformList.remove(i);
        }
    }

    System.out.println(platformList);
}

初始下标状态与正序示例相同。删除“掘金”(下标2)后,列表状态变为:
倒序删除后下标变化示意图

由于遍历方向是从后向前,元素的删除不会影响尚未遍历到的索引位置,因此无需任何额外调整。

5. 使用Stream filter过滤

对于Java 8及以上版本,利用Stream API进行过滤是一种更函数式、更简洁的方案:

public static void main(String[] args) {
    List<String> platformList = new ArrayList();
    platformList.add("博客园");
    platformList.add("CSDN");
    platformList.add("掘金");
    platformList = platformList.stream().filter(e -> !e.startsWith("博客园")).collect(Collectors.toList());
    System.out.println(platformList);
}

运行结果如下:
Stream过滤运行结果截图

这种方法并非直接修改原列表,而是通过流式操作筛选出符合条件的元素并收集到一个新列表中。代码意图清晰,且完全避免了并发修改异常。

总结来说,在Java中循环删除List元素时,应避免直接在使用foreach循环中操作原列表。根据具体场景,灵活选用Iterator.remove()、手动管理下标的for循环或Stream API,可以既安全又高效地完成任务。




上一篇:Redis部署模式深度演进:从单实例到Cluster集群的实战解析
下一篇:我在Bugcrowd计划中发现MCP服务器权限绕过漏洞的完整复盘
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 02:48 , Processed in 0.330783 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表