java循环删除List元素报错的原因分析与解决

 更新时间:2023年11月03日 09:05:02   作者:三火哥  
大家在工作中应该都会遇到从List集合中删除某一个或多个元素的业务场景,相信大家都会避开在循环里面删除元素,使用其他方式处理,这是为什么呢,下面小编就来和大家详细聊聊

描述

大家在工作中应该都会遇到从List集合中删除某一个或多个元素的业务场景

相信大家都会避开在循环里面删除元素,使用其他方式处理

很多面试官也都会问为什么循环里面不能删除元素?

示例

public static void main(String[] args) {  
    List<String> list = new ArrayList<>();  
    list.add("test0");  
    list.add("test1");  
    list.add("test2");  
    list.add("test3");  
    list.add("test4");  
    list.add("test5");  
    System.out.println(list);  

    list.remove(3);  
    System.out.println(list);  

    list.remove("test1");  
    System.out.println(list);  

    for (int i = 0; i < list.size(); i++) {  
        if (i == 2) {  
            list.remove(i);  
        }  
    }  
    System.out.println(list);  

    Iterator<String> it = list.iterator();  
    while (it.hasNext()) {  
        if (it.next().equals("test2")) {  
            it.remove();  
        }  
    }  
    System.out.println(list);  

    for (String s : list) {  
        if ("test5".equals(s)) {  
            list.remove(s);  
        }  
    }  
    System.out.println(list); 
}

//打印结果
[test0, test1, test2, test3, test4, test5]
[test0, test1, test2, test4, test5]
[test0, test2, test4, test5]
[test0, test2, test5]
[test0, test5]
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.fc.store.Test.main(Test.java:46)

从打印结果可以看到

  • 根据索引删除 --正常
  • 根据元素删除 --正常
  • 循环根据索引删除 --正常
  • 迭代删除原始 --正常
  • 循环根据元素删除 --不正常

执行过程

List<String> list = new ArrayList<>();
//集合初始化时 
transient Object[] elementData;  //空数组
private int size;  //长度为0
protected transient int modCount = 0;   //修改次数为0

//添加元素
list.add("test0"); 
elementData[0] = "test0";
size = 1;
modCount = 1;

list.add("test1"); 
elementData[0] = "test0";
elementData[1] = "test1";
size = 2;
modCount = 2;

list.add("test2"); 
elementData[0] = "test0";
elementData[1] = "test1";
elementData[2] = "test2";
size = 3;
modCount = 3;

list.add("test3"); 
elementData[0] = "test0";
elementData[1] = "test1";
elementData[2] = "test2";
elementData[3] = "test3";
size = 4;
modCount = 4;

list.add("test4");
elementData[0] = "test0";
elementData[1] = "test1";
elementData[2] = "test2";
elementData[3] = "test3";
elementData[4] = "test4";
size = 5;
modCount = 5;

list.add("test5");
elementData[0] = "test0";
elementData[1] = "test1";
elementData[2] = "test2";
elementData[3] = "test3";
elementData[4] = "test4";
elementData[5] = "test5";
size = 6;
modCount = 6;

//可以发现每添加一个元素,集合size会增加1, 修改次数会增加1

//根据索引删除
list.remove(3);
elementData[0] = "test0";
elementData[1] = "test1";
elementData[2] = "test2";
elementData[4] = "test4";
elementData[5] = "test5";
size = 5;
modCount = 7;

//根据元素删除
list.remove("test1");
elementData[0] = "test0";
elementData[1] = "test2";
elementData[2] = "test4";
elementData[5] = "test5";
size = 4;
modCount = 8;

//循环根据索引删除
for (int i = 0; i < list.size(); i++) {  
    if (i == 2) {  
        list.remove(i);  
    }  
}
elementData[0] = "test0";
elementData[1] = "test2";
elementData[5] = "test5";
size = 3;
modCount = 9;

//循环根据索引删除
Iterator<String> it = list.iterator();  
while (it.hasNext()) {  
    if (it.next().equals("test2")) {  
        it.remove();  
    }  
}  
elementData[0] = "test0";
elementData[5] = "test5";
size = 2;
modCount = 10;

//可以发现每删除一个元素后,集合size会减1,修改次数会增加1

//循环根据元素删除
for (String s : list) {  
    if ("test2".equals(s)) {  
        list.remove(s);  
    }  
}
//就抛异常了,因为Iterator的next方法会校验是否修改
//此时:expectedModCount = 10, modCount = 11

public E next() {
    checkForComodification();
}

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

源码解析

List 的add和remove方法每次操作都会对modCount++

public boolean add(E e) {  
    ensureCapacityInternal(size + 1); // Increments modCount!!  
    elementData[size++] = e;  
    return true;  
}

public E remove(int index) {  
    rangeCheck(index);  
    modCount++;  
    E oldValue = elementData(index);  
    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  
    return oldValue;  
}

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(index);  
                return true; 
            }
        }  
    }  
    return false;  
}

private void fastRemove(int index) {  
    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  
}

private void ensureCapacityInternal(int minCapacity) {  
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));  
}  
  
private void ensureExplicitCapacity(int minCapacity) {  
    modCount++;  
}

Iterator 会继承List的modCount,并赋值给expectedModCount

private class Itr implements Iterator<E> {  
    int cursor; // index of next element to return  
    int lastRet = -1; // index of last element returned; -1 if no such  
    int expectedModCount = modCount;
}

Iterator 的next方法会校验modCount和expectedModCount 是否相同

public E next() {  
    checkForComodification();  
    int i = cursor;  
    if (i >= size)  
        throw new NoSuchElementException();  
    Object[] elementData = ArrayList.this.elementData;  
    if (i >= elementData.length)  
        throw new ConcurrentModificationException();  
    cursor = i + 1;  
    return (E) elementData[lastRet = i];  
}
final void checkForComodification() {  
    if (modCount != expectedModCount)  
        throw new ConcurrentModificationException();  
}

由于在Iterator的便利中使用了List的remove方法,导致modCount增加了 所以在下次next方法中判断modCount和expectedModCount不一致就直接抛出了异常

解决方案

第一种使用迭代器删除

Iterator<String> it = list.iterator();  
while (it.hasNext()) {  
    if (it.next().equals("test2")) {  
        it.remove();  
    }  
}  

第二种for循环删除后要立即退出

for (String s : list) {  
    if ("test5".equals(s)) {  
        list.remove(s);  
        return;  
    }  
}

//JAVA8语法
list.removeIf("test5"::equals);

Set,Map 同理

同样Set,Map循环里面删除报错也是同样的原理

往往大家在遇到问题后,都只是找到了主要原因,但是并没有找到根本原因。

只有通过深入分析找到根本原因,制定预防措施(加入CR清单,添加扫码规则,制定奖惩措施),才能够真正避免问题

到此这篇关于java循环删除List元素报错的原因分析与解决的文章就介绍到这了,更多相关java循环删除元素内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java面向对象编程(封装/继承/多态)实例解析

    Java面向对象编程(封装/继承/多态)实例解析

    这篇文章主要介绍了Java面向对象编程(封装/继承/多态)实例解析的相关内容,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • java字符串压缩解压示例

    java字符串压缩解压示例

    这篇文章主要介绍了java字符串压缩解压示例,先压缩,再加密,再压缩,数据越大,压缩比例越高,需要的朋友可以参考下
    2014-03-03
  • 详解OpenAPI开发如何动态的添加接口实现

    详解OpenAPI开发如何动态的添加接口实现

    这篇文章主要为大家介绍了OpenAPI开发如何动态的添加接口实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • Spring控制Bean加载顺序的操作方法

    Spring控制Bean加载顺序的操作方法

    正常情况下,Spring 容器加载 Bean 的顺序是不确定的,那么我们如果需要按顺序加载 Bean 时应如何操作?本文将详细讲述我们如何才能控制 Bean 的加载顺序,需要的朋友可以参考下
    2024-05-05
  • HashMap在JDK7与JDK8中的实现过程解析

    HashMap在JDK7与JDK8中的实现过程解析

    这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,很多文章都是旧版本JDK1.6.JDK1.7的。现在我来分析下JDK7与JDK8中HashMap的实现过程
    2021-09-09
  • 利用JAVA反射,读取数据库表名,自动生成对应实体类的操作

    利用JAVA反射,读取数据库表名,自动生成对应实体类的操作

    这篇文章主要介绍了利用JAVA反射,读取数据库表名,自动生成对应实体类的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • Java类的访问权限关键字用法说明

    Java类的访问权限关键字用法说明

    这篇文章主要介绍了Java类的访问权限关键字用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot @Configuration与@Bean注解使用介绍

    SpringBoot @Configuration与@Bean注解使用介绍

    这篇文章主要介绍了SpringBoot中的@Configuration与@Bean注解,在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制,只有把这些都搞清楚了,我们在之后使用才会更加得心应手
    2022-10-10
  • springboot实现用户名查找用户功能

    springboot实现用户名查找用户功能

    本文主要介绍了springboot实现用户名查找用户功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • IDEA集成Sonar的完整流程

    IDEA集成Sonar的完整流程

    这篇文章主要介绍了IDEA集成Sonar的完整流程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06

最新评论