List删除异常原理

fori删除

fori 删除导致的异常

会报 indexOutBoundsException

fori循环是通过获取list中object\[\]数组的下标来循环获取数据的。一旦执行 list.remove(i) List中数组的数据

Object\[\] elementData 会执行 copy操作,数组index之后的所有数据会往前挪一位,同时把记录数组数量的字段size -1。

在这种情况下,当 i 为 2 时,list.remove(i) 会移除元素 "a",然后 ArrayList 会自动将后续元素前移并更新 size,使得原本在索引位置 2 的元素被移除,导致当前的 i 指向了下一个元素,但我们仍然继续访问原来的 i 索引位置,最终触发 IndexOutOfBoundsException。

csharp 复制代码
    List<String> list = new ArrayList<>();
        list.add("b");
        list.add("c");
        list.add("a"); 
//会报查询索引的长度 < 数组的长度
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals("a")) {
                list.remove(i);
            }
            // #1
            System.out.println(list.get(i));
        }

foreach删除

foreach 删除导致的异常

会抛出 ConcurrentModificationException异常

erlang 复制代码
for (String s : list){
    if (s.equals("a")){
       list.remove(s);
    }
}

foreach是语法糖,是为了java程序员加速开发做的优化,因为虚拟机是不认识foreach语法的,经过javac编译之后会变成原本的样子 虚拟机才能执行。

vbnet 复制代码
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    //#1 这里是关键点  这里会抛出 `ConcurrentModificationException`
    String s = iterator.next();
    if (s.equals("a")) {
        //#2 
        list.remove(s); 
    }
}

Interator是List内部的类,它的next方法如下,他会调用checkForComodification() , 这个方法会在校验expectedModCount与modCount。

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

初始化 Iterator的时候(这里是内部类class Itr)modCount与expectedModCount一致都是更改次数。当调用list.remove()的时候

会调用 fastMove()方法。而这个方法是把modCount++ 增加了1。而下次再循环的时候还会执行 Iterator.next()方法就会导致 expectedModCount与modCount不相等抛异常了。

正确的方式

与上面的区别是 注意#1 位置,不要用list.remove() ,具体为啥上面已经说了。那为什么用iterator.remove就不会报错了了呢?

vbnet 复制代码
        //不会报异常
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            if (next.equals("a")) {
                //#1 这里不要用list.remove
                iterator.remove();
            }
        }

iterator.remove()源码如下 注意 #2的位置,这里在删除之后会强行吧 expectedModCount = modCount这2个参数设置成一致,所以再调用.next()就不会抛出不一致的异常了。

csharp 复制代码
 public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
               //调用父类的删除
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                // #2
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

ConcurrentModificationException 是为了避免并发修改引发不一致的状态,这个异常表明在遍历集合时集合结构被修改了(如通过 remove 方法)。Java 的 Iterator 保证在遍历时结构不会被改变,因此需要使用 iterator.remove() 来安全地删除元素。

相关推荐
panxianren7 小时前
Bun 1.4 的 bun test --parallel 将 4 秒的测试套件缩短到约 1 秒
java·javascript
sunshine22 girl9 小时前
Java学习七 Java 项目实战2-注册业务
java·学习
省钱兄--zs9 小时前
北京24小时自助健身房系统软件开发实战:从架构设计到部署指南
java·数据仓库·spring boot·小程序·需求分析
youdexiang9 小时前
Android录音软件时间关键词检索功能分析
java·人工智能
sunshine22 girl10 小时前
Java学习七 Java 项目实战1-项目创建和主页面搭建
java·开发语言·学习
code2cat10 小时前
Java进阶篇之AtomicStampedReference:引用回到原值,版本戳仍能记住变化
java·cas·并发编程·aba
泡茶喝茶写代码12 小时前
A股量化数据工程:从 REST 接口到策略信号(第 8 篇):成长能力因子:营收与利润增速
java·python·股票数据api·股票数据api接口·股票量化数据api·股票量化数据接口·股票数据api数据
\光辉岁月/13 小时前
7.javase-面向对象
java·开发语言
weixin_5310946913 小时前
heap_4内存管理
java·开发语言·算法
程序员南飞13 小时前
win系统安装minio
java·数据结构·算法