foreach遍历list删除元素一定会报错?
foreach遍历list集合删除某些元素一定会报错吗?
先上一段代码:
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
for (String item : list) {if (item.equals("3")) {System.out.println(item);list.remove(item);}
}
System.out.println(list.size());
控制台报错:java.util.ConcurrentModificationException。
这是怎么回事,然后去看了看这个异常,才发现自己果然还是太年轻啊。
我们都知道增加for循环即foreach循环其实就是根据list对象创建一个iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了iterator,如果要对list进行增删操作,都必须经过iterator。
每次foreach循环时都有以下两个操作
- iterator.hasNext(); //判读是否有下个元素
- item = iterator.next(); //下个元素是什么,并把它赋给item。
首先,我们来看看这个异常信息是什么
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];
}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();
}
可以看到是进入checkForComodification()方法的时候报错了,也就是说modCount != expectedModCount。具体的原因是:以foreach方式遍历元素的时候,会生成iterator,然后使用iterator遍历。在生成iterator的时候,会保存一个expectedModCount参数,这个是生成iterator的时候期望List中修改元素的次数。如果你在遍历过程中删除元素,List中modCount就会变化
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 remove method that skips bounds checking and does not* return the value removed.*/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和exceptedModCount不一致,就会抛出异常,这个是为了安全考虑
但是,遍历list删除元素使用Iterator则不会报错,如下:
Iterator it = list.iterator();
while (it.hasNext()) {if (it.next().equals("3")) {it.remove();}
}
看看Iterator的remove()方法的源码,是对expectedModCount重新做了赋值处理的,如下
public void remove() {if (lastRet < 0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount; // 处理expectedModCount} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}
}
这样的话保持expectedModCount = modCount相等,就不会报出错了。
是不是foreach所有的list删除操作都会报出这个错呢?
其实不一定。如果删除的元素是倒数第二个数的话,其实是不会报错的。为什么呢,来一起看看。
之前说了foreach循环会走两个方法hasNext() 和next()。如果不想报错的话,只要不进next()方法就好啦,看看hasNext()的方法。
public boolean hasNext() {return cursor != size;
}
那么就要求hasNext()的方法返回false了,即cursor == size。其中cursor是Itr类(Iterator子类)中的一个字段,用来保存当前iterator的位置信息,从0开始。cursor本身就是游标的意思,在数据库的操作中用的比较多。只要curosr不等于size就认为存在元素。由于Itr是ArrayList的内部类,因此直接调用了ArrayList的size字段,所以这个字段的值是动态变化的,既然是动态变化的可能就会有问题出现了。
我们以上面的代码为例,当到倒数第二个数据也就是“4”的时候,cursor是4,然后调用删除操作,此时size由5变成了4,当再调用hasNext判断的时候,cursor==size,就会调用后面的操作直接退出循环了。我们可以在上面的代码添加一行代码查看效果
for (String item : list) {System.out.println(item);if (item.equals("4")) {list.remove(item);}
}
输出是:
1
2
3
4
这样的话就可以看到执行到hasNext()方法就退出了,最后的5没有遍历到,也就不会走后面的异常了。
由此可以得出,用foreach删除list元素的时候只有倒数第二个元素删除不会报错,其他都会报错,所以删除list元素时一定要用Iterator。
作者:Java_Explorer
链接:https://www.jianshu.com/p/cebdb46df4b0
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
foreach遍历list删除元素一定会报错?相关推荐
- 关于foreach遍历list删除元素
增强for循环--foreach循环其实就是根据list对象建立的一个iterator迭代器对象,用这个迭代器来遍历list,若要对list进行增删操作,都必须通过iterator. 每次foreac ...
- 【千律】C++基础:map 循环遍历删除元素,及其报错的解决方案
报错原因:采用erase移除迭代器后,迭代器的值变为-572662307,无法作为迭代器继续运算. 详细:当程序执行到 stu_map.erase(itor) 时,满足条件的第一个元素被删除,从而导致 ...
- 遍历删除List中的元素,会报错? 用iterator.remove() 完美解决
经常会碰到遍历集合,然后删除里面的对象报错, 纠结半天, 百度了一下,有大神说不能用for-each, for , 只能用迭代器,真的吗? 我就删成功了呢,看代码,请大神们指正! publi ...
- hashmap移除元素_Java HashMap 如何正确遍历并删除元素的方法小结
(一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap myHa ...
- cocos2d-x CCArray用法 遍历和删除元素
本文为 justbilt 原创,转载请标明原作者及原文出处,以示尊重! 作者:justbilt 原文:http://blog.justbilt.com/25/ 一.基本用法 1.声明初始化变量 C++ ...
- 浅谈为什么倒序遍历List删除元素没有问题
要搞清楚这个问题,首先要知道如何正确的遍历List删除元素.注:下述代码完整版附在末尾. 先给出这次测试的list初始化结构: list.add("a");list.add(&qu ...
- C++ vector容器遍历并删除元素
在使用C++ vector的迭代器遍历并删除元素时,存在一些不注意的误区,这里特此记录. 在使用迭代器遍历vector元素时,错误的删除方法: vector<int>::iterator ...
- List遍历中删除元素
List遍历主要有索引下标遍历.for循环遍历和Iterator迭代遍历,索引下标和for循环在遍历中删除元素都存在问题,Iterator迭代可以实现遍历中删除元素. 索引下标遍历 List<I ...
- MyEclipse在删除文件后servers报错问题解决
MyEclipse在删除文件后servers报错"Could not create the view: An unexpected exception was thrown." 解 ...
最新文章
- 这所高校招收佛学研究生,面试需要写论文,毕业后安排去向,就业前景好!...
- are exo exo是什么歌 we_are exo exo是什么歌 we_EXO we are one
- Docker进阶(制作镜像,共享卷,网络通信,私有仓库)
- 菜鸟学习笔记:Java提升篇4(容器4——Collections工具类、其他容器)
- mac地址修改_Mac 地址是什么?Mac 地址的修改及妙用!
- 磁盘IOPS计算与测量
- 关于名为民间借贷实为诈骗案件的讨论
- 玩机技巧|去除Windows桌面快捷方式图标左下角上的小箭头
- 2022 CNCC 中国计算机大会参会总结
- 读书虽苦,却是最容易的那条!
- js 删除数组元素。
- 【SpringBoot进阶】阿里云短信发送配置
- 字符串 转16进制 sscanf
- android移动支付——微信支付
- 无功补偿的原理和形式
- Kotlin的高价函数—apply、aslo、let、run的使用总结
- Javascript 刷新页面的几种常用方法
- 域适应(domain adaptation)
- 作曲 app android,文艺又好玩!安卓作曲达人App试用体验
- 【2022-09-14】米哈游秋招笔试三道编程题