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

相关推荐
用户05956611920922 分钟前
现代应用中 Java 集合框架的核心技术与实践解析
java·后端
t198751281 小时前
Java连接Redis和基础操作命令
java·开发语言·redis
秋名RG1 小时前
深入理解享元模式:用Java实现高效对象共享
java·开发语言·享元模式
master-dragon1 小时前
设计模式-观察者模式
java·观察者模式·设计模式
小白本无忧1 小时前
PHP 垃圾回收高级特性
android·java·php
何中应1 小时前
【设计模式-3.5】结构型——装饰器模式
java·设计模式·装饰器模式
MonKingWD1 小时前
【JVM】Java程序运行时数据区
java·jvm
杀神lwz1 小时前
JVM学习(六)--垃圾回收
java·jvm·学习
一刀到底2111 小时前
java 微服务中,微服务相互调用 feign 和flux 如何选择
java·开发语言·微服务
保持学习ing1 小时前
黑马Java面试笔记之 微服务篇(SpringCloud)
java·笔记·后端·阿里云·面试·负载均衡·springcloud