之前遇到对List进行遍历删除的时候,出现来一个ConcurrentModificationException 异常,可能好多人都知道list遍历不能直接进行删除操作,但是你可能只是跟我一样知道结果,但是不知道为什么不能删除,或者说这个报错是如何产生的,那么我们今天就来研究一下。

 

一、异常代码

我们先看下这段代码,你有没有写过类似的代码

public static void main(String[] args) {List<Integer> list = new ArrayList<>();System.out.println("开始添加元素 size:" + list.size());for (int i = 0; i < 100; i++) {list.add(i + 1);}System.out.println("元素添加结束 size:" + list.size());Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {Integer next = iterator.next();if (next % 5 == 0) {list.remove(next);}}System.out.println("执行结束 size:" + list.size());
}

「毫无疑问,执行这段代码之后,必然报错,我们看下报错信息。」

我们可以通过错误信息可以看到,具体的错误是在checkForComodification 这个方法产生的。

 

二、ArrayList源码分析

首先我们看下ArrayListiterator这个方法,通过源码可以发现,其实这个返回的是ArrayList内部类的一个实例对象。

public Iterator<E> iterator() {return new Itr();
}

我们看下Itr类的全部实现。

private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;Itr() {}public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")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];}public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}@Override@SuppressWarnings("unchecked")public void forEachRemaining(Consumer<? super E> consumer) {Objects.requireNonNull(consumer);final int size = ArrayList.this.size;int i = cursor;if (i >= size) {return;}final Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();}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();}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

「参数说明:」

cursor : 下一次访问的索引;

lastRet :上一次访问的索引;

expectedModCount :对ArrayList修改次数的期望值,初始值为modCount

modCount :它是AbstractList的一个成员变量,表示ArrayList的修改次数,通过addremove方法可以看出;

「几个常用方法:」

hasNext():

public boolean hasNext() {return cursor != size;
}

如果下一个访问元素的下标不等于size,那么就表示还有元素可以访问,如果下一个访问的元素下标等于size,那么表示后面已经没有可供访问的元素。因为最后一个元素的下标是size()-1,所以当访问下标等于size的时候必定没有元素可供访问。

next()

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];
}

注意下,这里面有两个非常重要的地方,cursor初始值是0,获取到元素之后,cursor 加1,那么它就是下次索要访问的下标,最后一行,将i赋值给了lastRet这个其实就是上次访问的下标。

此时,cursor变为了1,lastRet变为了0。

最后我们看下ArrayListremove()方法做了什么?

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
}

「重点:」

我们先记住这里,modCount初始值是0,删除一个元素之后,modCount自增1,接下来就是删除元素,最后一行将引用置为null是为了方便垃圾回收器进行回收。

 

三、问题定位

到这里,其实一个完整的判断、获取、删除已经走完了,此时我们回忆下各个变量的值:

cursor : 1(获取了一次元素,默认值0自增了1);

lastRet :0(上一个访问元素的下标值);

expectedModCount :0(初始默认值);

modCount :1(进行了一次remove操作,变成了1);

不知道你还记不记得,next()方法中有两次检查,如果已经忘记的话,建议你往上翻一翻,我们来看下这个判断:

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

modCount不等于expectedModCount的时候抛出异常,那么现在我们可以通过上面各变量的值发现,两个变量的值到底是多少,并且知道它们是怎么演变过来的。那么现在我们是不是清楚了ConcurrentModificationException异常产生的愿意呢!

「就是因为,list.remove()导致modCountexpectedModCount的值不一致从而引发的问题。」

 

四、解决问题

我们现在知道引发这个问题,是因为两个变量的值不一致所导致的,那么有没有什么办法可以解决这个问题呢!答案肯定是有的,通过源码可以发现,Iterator里面也提供了remove方法。

public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}
}

你看它做了什么,它将modCount的值赋值给了expectedModCount,那么在调用next()进行检查判断的时候势必不会出现问题。

那么以后如果需要remove的话,千万不要使用list.remove()了,而是使用iterator.remove(),这样其实就不会出现异常了。

public static void main(String[] args) {List<Integer> list = new ArrayList<>();System.out.println("开始添加元素 size:" + list.size());for (int i = 0; i < 100; i++) {list.add(i + 1);}System.out.println("元素添加结束 size:" + list.size());Iterator<Integer> iterator = list.iterator();while (iterator.hasNext()) {Integer next = iterator.next();if (next % 5 == 0) {iterator.remove();}}System.out.println("执行结束 size:" + list.size());
}

「建议:」

另外告诉大家,我们在进行测试的时候,如果找不到某个类的实现类,因为有时候一个类有超级多的实现类,但是你不知道它到底调用的是哪个,那么你就通过debug的方式进行查找,是很便捷的方法。

 

五、总结

其实这个问题很常见,也是很简单,但是我们做技术的就是把握细节,通过追溯它的具体实现,发现它的问题所在,这样你不仅仅知道这样有问题,而且你还知道这个问题具体是如何产生的,那么今后不论对于你平时的工作还是面试都是莫大的帮助。

本期分享就到这里,谢谢各位看到此处,

记得点个赞呦!

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

移除List中的元素,你的姿势对了吗?相关推荐

  1. 【JS实战】移除数组中的元素(返回新的数组)

    移除数组中的元素(返回新的数组) 方法一:for循环+push function remove(arr, item) {let newArr = [];for(let i = 0; i < ar ...

  2. js移除数组中的元素(返回新数组)

    移除数组中的元素(返回新数组) 移除数组 arr 中的所有值与 item 相等的元素.不要直接修改数组 arr,结果返回新的数组. 输入:[1,2,3,4,2] ,2 输出:[1,3,4] 第一种解决 ...

  3. Java 移除List中的元素,这玩意讲究!

    前言 前阵子,一名java初学者 遇到了list 使用remove的问题,当时我暂且给他说了一种解决方案. 事后,我细想, 是不是很多初学者都会碰到这种问题? 虽然阿里开发手册里面有说到这个坑,但是是 ...

  4. 3. JS编程之移除数组中的元素

    版本一:不修改原数组,返回新数组 题目描述 移除数组 arr 中的所有值与 item 相等的元素.不要直接修改数组 arr,结果返回新的数组. 示例 //输入 [1, 2, 3, 4, 2], 2 / ...

  5. 移除数组中指定元素-python

    移除元素-力扣 思路:双指针 fast指针指向当前位置,slow指针指向新数组的新元素的位置,新数组就是没有val的数组 1)当fast指向的数值不等于val,则这个值一定是新数组的元素,将fast指 ...

  6. 删除list集合中特定元素的正确姿势

    背景 如何删除一个集合对象中的特定元素?小问题,但并不简单. 常见异常: ConcurrentModificationException java.util.ConcurrentModificatio ...

  7. js移除Array中指定元素

    首先需要找到元素的下标: var array = [2, 5, 9]; var index = array.indexOf(5); 使用splice函数进行移除: if (index > -1) ...

  8. js移除数组中指定元素

    首先需要找到元素的下标: var array = ["zhangsan", "lisi", "wangwu"]; var index = a ...

  9. python移除字符串中指定元素_Python – 从作为另一个元素的子串的字符串列表中删除任何元素...

    第一个构建块:子串. 您可以使用来检查: >>> 'rest' in 'resting' True >>> 'sing' in 'resting' False 接下 ...

最新文章

  1. mxnet 和pytorch比较
  2. 推荐一个Android开源项目-AisenWeiBo
  3. ExtJS4.2:自定义主题 入门
  4. 6年Java程序员年薪60W,这些年我都爬过哪些坑(2021年终总结)
  5. python中深拷贝和浅拷贝
  6. 使用ASP.NET Core 3.x 构建 RESTful API - 2. 什么是RESTful API
  7. CF1444C-Team-Building【可撤销并查集】
  8. ABP理论学习之Web API控制器(新增)
  9. a算法TSP旅行商java_A*算法实现旅行商问题(人工智能报告,付代码)
  10. Android 系统(247)---Android Go项目, Launcher no Notification Dots
  11. UML建模之活动图介绍(Activity Diagram)
  12. jquery 实现图片上传,并在前端显示出来
  13. 【图像提取】基于matlab PCA-CSIFT feature图像特征提取【含Matlab源码 1174期】
  14. 减小pdf大小 打印 低分辨率
  15. Lucene 深入学习(3)Lucene索引初识
  16. OpenERP中商品销售的处理及案例解析
  17. 蜀门一直显示连接服务器,蜀门进不去点进入之后,出现无法连接服务器 – 手机爱问...
  18. 微信h5互动小游戏制作心得
  19. oss2罗列所有文件
  20. 如何对移动端的图片流量进行优化

热门文章

  1. cmd编译整个java项目_cmd中使用javac对整个包编译怎么办?对包里面的某几个java文件编译怎么办?...
  2. css 文字重叠_html网页文字重叠 字体叠加显示css如何解决
  3. 10无法更新系统_华为EMUI系统或停留安卓11,无法更新
  4. view.post(Runnable)
  5. loadrunner自学笔记-性能测试的注意要点
  6. (软件工程复习核心重点)第十章面向对象设计-第四节:设计人机交互子系统和设计任务管理子系统
  7. libevent evhttp学习——http服务端
  8. 数据库事务及事务的特征
  9. LeetCode 40 组合总和 II
  10. Linux OpenSSL获取证书指纹值(443、MD5、SHA1、SHA256)