for-each或迭代器中调用List的remove方法会抛出ConcurrentModificationException的原因

简介: for-each循环遍历的实质是迭代器,使用迭代器的remove方法前必须调用一下next()方法,并且调用一次next()方法后是不允许多次调用remove方法的,为什么呢?接下来一起来看吧

for-each循环遍历的实质是迭代器,使用迭代器的remove方法前必须调用一下next()方法,并且调用一次next()方法后是不允许多次调用remove方法的,为什么呢?接下来一起来看吧


publicvoidremove(ArrayList<Integer>list) {
for (Integerinteger : list) {
if (integer==5) {
list.remove(integer);
            }
        }
    }

image.gif

比如这一段代码会抛出ConcurrentModificationException,为什么呢,请看源码


publicIterator<E>iterator() {
returnnewItr();
    }
/*** An optimized version of AbstractList.Itr*/privateclassItrimplementsIterator<E> {
intcursor;       // index of next element to returnintlastRet=-1; // index of last element returned; -1 if no suchintexpectedModCount=modCount;
publicbooleanhasNext() {
returncursor!=size;
        }
@SuppressWarnings("unchecked")
publicEnext() {
checkForComodification();
inti=cursor;
if (i>=size)
thrownewNoSuchElementException();
Object[] elementData=ArrayList.this.elementData;
if (i>=elementData.length)
thrownewConcurrentModificationException();
cursor=i+1;
return (E) elementData[lastRet=i];
        }
publicvoidremove() {
if (lastRet<0)
thrownewIllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor=lastRet;
lastRet=-1;
expectedModCount=modCount;
            } catch (IndexOutOfBoundsExceptionex) {
thrownewConcurrentModificationException();
            }
        }
@Override@SuppressWarnings("unchecked")
publicvoidforEachRemaining(Consumer<?superE>consumer) {
Objects.requireNonNull(consumer);
finalintsize=ArrayList.this.size;
inti=cursor;
if (i>=size) {
return;
            }
finalObject[] elementData=ArrayList.this.elementData;
if (i>=elementData.length) {
thrownewConcurrentModificationException();
            }
while (i!=size&&modCount==expectedModCount) {
consumer.accept((E) elementData[i++]);
            }
// update once at end of iteration to reduce heap write trafficcursor=i;
lastRet=i-1;
checkForComodification();
        }
finalvoidcheckForComodification() {
if (modCount!=expectedModCount)
thrownewConcurrentModificationException();
        }
    }

image.gif

实质是变化为迭代器实现,不管是迭代器里面的remove()还是next()方法,都会checkForComodification();而这个方法是判断modCount和expectedModCount是否相等,这个modCount是这个list集合修改的次数,每一次add或者remove都会增加这个变量


image.gif


然后迭代器每次去next或者去remove的时候检查checkForComodification();发现expectedModCount(这个迭代器修改的次数)和modCount(这个集合实际修改的次数)不相等,就会抛出ConcurrentModificationException


image.gif


迭代器里面没有add方法,用迭代器时,可以删除原来集合的元素,但是!一定要用迭代器的remove方法而不是集合自身的remove方法,否则抛异常。


再来看一个例子,这个是否正确?


publicvoidremove(ArrayList<Integer>list) {
Iterator<Integer>it=list.iterator();
while (it.hasNext()) {
it.remove();
    }
}

image.gif

这个第一次循环就会抛出IllegalStateException异常,为什么呢?那么我们再次看到迭代的remove方法


image.gif


关于lastRet,我们后面再讲,来看看,上一次remove()之后,lastRet变成了-1,然后没有it.next();继续执行remove(),首先判断if(lastRet<0)的时候就抛出了这个IllegalStateException异常。所以刚刚的代码是错的。

为什么要it.next()?难道next()方法又改了lastRet吗?它确确实实更改了。


image.gif


lastRet被赋值为i就不是-1了,下一次需要remove的时候就不会抛出IllegalStateException

那再来看看这个相同原理的例子


publicvoidremove(ArrayList<Integer>list) {
Iterator<Integer>it=list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
it.remove();
    }
}

image.gif

这个正确吗?刚刚已经说了这个是错误的会抛出IllegalStateException异常,因为remove()调用一次后lastRet会变成-1,第二个remove()在第一句if(lastRet<0)的时候就异常了。


正确的使用:


publicvoidremove(ArrayList<Integer>list) {
Iterator<Integer>it=list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
    }
}

image.gif

这里可不允许it.next()和it.remove()顺序交换!

因为lastRet的值初始化是-1,所以如果先remove()就会抛出IllegalStateException异常。


image.gif


综上

1.在for-each循环和迭代器中只可以做删除remove操作,不能做添加add操作。想要删除集合中的元素必须用迭代器的remove方法,不能添加操作add,因为add也会修改集合的modCount导致ConcurrentModificationException


2.用迭代器的remove()前必须调用一下next()方法,否则IllegalStateException


3.调用一次next()方法后是不允许多次调用remove方法,否则IllegalStateException


=========================Talk is cheap, show me the code========================

目录
相关文章
|
存储 运维 监控
提升数据中心效率的关键策略:自动化运维流程
【4月更文挑战第20天】随着企业对数据处理和存储需求的不断增长,数据中心的效率成为了优化的焦点。在本文中,我们将探讨自动化在数据中心运维中的应用,并分析其如何显著提升操作效率、降低成本以及提高服务水平。通过案例研究和最佳实践的分享,读者将获得实施自动化运维流程的实用指南,以支持其在不断变化的技术环境中保持竞争力。
用户态和内核态是如何切换的?
【10月更文挑战第28天】用户态和内核态的切换是通过系统调用指令、异常和中断等机制来实现的。这些机制确保了应用程序能够在需要时请求内核提供的服务,同时也保证了内核能够对系统资源进行有效的管理和保护,维护系统的稳定性和安全性。通过准确地保存和恢复上下文信息,实现了用户态和内核态之间的无缝切换,为计算机系统的正常运行提供了有力保障。
|
缓存 前端开发 API
探索PWA(Progressive Web Apps)的无限可能
探索PWA(Progressive Web Apps)的无限可能
1236 8
|
存储 Linux Shell
Linux|Transfer.sh 轻松实现文件共享
Linux|Transfer.sh 轻松实现文件共享
Linux|Transfer.sh 轻松实现文件共享
|
存储 数据挖掘 Linux
探索Linux命令rpm2cpio:解析RPM包内容的利器
`rpm2cpio`是Linux下用于从RPM包中提取内容的工具,它将`.rpm`转换为CPIO归档。无需安装,可直接访问包内文件,适合数据分析。命令简单,常与`cpio`结合使用,如`rpm2cpio package.rpm | cpio -idmv`解压文件。示例包括提取特定文件和列出包内所有文件。注意权限、路径和文件完整性,使用前备份数据,并查阅文档以优化使用。
|
11月前
|
小程序 Java 知识图谱
Java 学习笔记 —— BMI & BMR 计算器
这是一个使用 Java 编写的 BMI 与 BMR 计算器小程序,可输入年龄、性别、身高和体重,计算身体质量指数(BMI)和基础代谢率(BMR),并输出健康评估结果。通过该项目,掌握了 Java 的输入处理、数据验证、条件判断、数学运算及格式化输出等基础知识,是 Java 初学者的理想练习项目。
|
Java
Java - java.util.ConcurrentModificationException 异常原因及解决方法(一)
Java - java.util.ConcurrentModificationException 异常原因及解决方法(一)
566 0
|
Java 索引
Java foreach中List移除元素抛出ConcurrentModificationException原因全解析
Java foreach中List移除元素抛出ConcurrentModificationException原因全解析
716 0
Java foreach中List移除元素抛出ConcurrentModificationException原因全解析
EasyUI combobox选中项变化事件存在的问题
本文目录 1. 背景 2. 写法 3. 实际存在的问题
788 0