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因项目配置不当而引发的数据泄露解决办法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • 利用spring aop实现动态代理

    利用spring aop实现动态代理

    这篇文章主要为大家详细介绍了利用spring aop实现动态代理的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • MybatisPlus 自定义插件实现拦截SQL修改功能(实例详解)

    MybatisPlus 自定义插件实现拦截SQL修改功能(实例详解)

    这篇文章主要介绍了MybatisPlus 自定义插件实现拦截SQL修改功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2023-11-11
  • Java中final变量使用总结

    Java中final变量使用总结

    这篇文章主要介绍了Java中final变量使用总结,final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值,通常final定义的变量为常量,需要的朋友可以参考下
    2015-06-06
  • SpringBoot父子线程数据传递的五种方案介绍

    SpringBoot父子线程数据传递的五种方案介绍

    在实际开发过程中我们需要父子之间传递一些数据,比如用户信息等。该文章从5种解决方案解决父子之间数据传递困扰,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-09-09
  • 基于@Valid和@Validated验证List集合的踩坑记录

    基于@Valid和@Validated验证List集合的踩坑记录

    这篇文章主要介绍了基于@Valid和@Validated验证List集合的踩坑记录,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Java中RSA加密解密的实现方法分析

    Java中RSA加密解密的实现方法分析

    这篇文章主要介绍了Java中RSA加密解密的实现方法,结合具体实例形式分析了java实现RSA加密解密算法的具体步骤与相关操作技巧,并附带了关于RSA算法密钥长度/密文长度/明文长度的参考说明,需要的朋友可以参考下
    2017-07-07
  • hibernate-validator如何使用校验框架

    hibernate-validator如何使用校验框架

    高效、合理的使用hibernate-validator校验框架可以提高程序的可读性,以及减少不必要的代码逻辑,本文主要介绍了hibernate-validator如何使用校验框架,感兴趣的可以了解一下
    2022-04-04
  • java基础的详细了解第三天

    java基础的详细了解第三天

    这篇文章对Java编程语言的基础知识作了一个较为全面的汇总,在这里给大家分享一下。需要的朋友可以参考,希望能给你带来帮助
    2021-08-08
  • JAVA swing布局管理器实例解析

    JAVA swing布局管理器实例解析

    这篇文章主要介绍了JAVA swing布局管理器实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03

最新评论