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() 来安全地删除元素。

相关推荐
~Yogi14 分钟前
每日学习Java之一万个为什么?(Maven篇+RPC起步+CICD起步)(待完善)
java·学习·maven
银之夏雪27 分钟前
ESLint 深度解析:原理、规则与插件开发实践
java·前端·javascript
重生之成了二本看我逆天改命走向巅峰39 分钟前
从0搭建Tomcat第二天:深入理解Servlet容器与反射机制
java·开发语言·笔记·学习·servlet·tomcat·idea
rkmhr_sef41 分钟前
Java进阶:Dubbo
java·开发语言·dubbo
不止会JS1 小时前
cursor使用经验分享(java后端服务开发向)
java·开发语言·经验分享·cursor
码熔burning1 小时前
(二 十 三)趣学设计模式 之 解释器模式!
java·设计模式·解释器模式
三水气象台1 小时前
对ArrayList中存储的TreeNode的排序回顾
java·数据结构·算法·huffman tree
牛马baby2 小时前
Java高频面试之集合-03
java·开发语言·面试
用手手打人2 小时前
多线程&JUC(二)
java·开发语言
剑走偏锋o.O2 小时前
Jedis学习笔记
java·redis·笔记·学习·jedis