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循环时都有以下两个操作

  1. iterator.hasNext(); //判读是否有下个元素
  2. 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删除元素一定会报错?相关推荐

  1. 关于foreach遍历list删除元素

    增强for循环--foreach循环其实就是根据list对象建立的一个iterator迭代器对象,用这个迭代器来遍历list,若要对list进行增删操作,都必须通过iterator. 每次foreac ...

  2. 【千律】C++基础:map 循环遍历删除元素,及其报错的解决方案

    报错原因:采用erase移除迭代器后,迭代器的值变为-572662307,无法作为迭代器继续运算. 详细:当程序执行到 stu_map.erase(itor) 时,满足条件的第一个元素被删除,从而导致 ...

  3. 遍历删除List中的元素,会报错? 用iterator.remove() 完美解决

     经常会碰到遍历集合,然后删除里面的对象报错, 纠结半天, 百度了一下,有大神说不能用for-each,  for , 只能用迭代器,真的吗?  我就删成功了呢,看代码,请大神们指正! publi ...

  4. hashmap移除元素_Java HashMap 如何正确遍历并删除元素的方法小结

    (一)HashMap的遍历 HashMap的遍历主要有两种方式: 第一种采用的是foreach模式,适用于不需要修改HashMap内元素的遍历,只需要获取元素的键/值的情况. HashMap myHa ...

  5. cocos2d-x CCArray用法 遍历和删除元素

    本文为 justbilt 原创,转载请标明原作者及原文出处,以示尊重! 作者:justbilt 原文:http://blog.justbilt.com/25/ 一.基本用法 1.声明初始化变量 C++ ...

  6. 浅谈为什么倒序遍历List删除元素没有问题

    要搞清楚这个问题,首先要知道如何正确的遍历List删除元素.注:下述代码完整版附在末尾. 先给出这次测试的list初始化结构: list.add("a");list.add(&qu ...

  7. C++ vector容器遍历并删除元素

    在使用C++ vector的迭代器遍历并删除元素时,存在一些不注意的误区,这里特此记录. 在使用迭代器遍历vector元素时,错误的删除方法: vector<int>::iterator ...

  8. List遍历中删除元素

    List遍历主要有索引下标遍历.for循环遍历和Iterator迭代遍历,索引下标和for循环在遍历中删除元素都存在问题,Iterator迭代可以实现遍历中删除元素. 索引下标遍历 List<I ...

  9. MyEclipse在删除文件后servers报错问题解决

    MyEclipse在删除文件后servers报错"Could not create the view: An unexpected exception was thrown." 解 ...

最新文章

  1. 这所高校招收佛学研究生,面试需要写论文,毕业后安排去向,就业前景好!...
  2. are exo exo是什么歌 we_are exo exo是什么歌 we_EXO we are one
  3. Docker进阶(制作镜像,共享卷,网络通信,私有仓库)
  4. 菜鸟学习笔记:Java提升篇4(容器4——Collections工具类、其他容器)
  5. mac地址修改_Mac 地址是什么?Mac 地址的修改及妙用!
  6. 磁盘IOPS计算与测量
  7. 关于名为民间借贷实为诈骗案件的讨论
  8. 玩机技巧|去除Windows桌面快捷方式图标左下角上的小箭头
  9. 2022 CNCC 中国计算机大会参会总结
  10. 读书虽苦,却是最容易的那条!
  11. js 删除数组元素。
  12. 【SpringBoot进阶】阿里云短信发送配置
  13. 字符串 转16进制 sscanf
  14. android移动支付——微信支付
  15. 无功补偿的原理和形式
  16. Kotlin的高价函数—apply、aslo、let、run的使用总结
  17. Javascript 刷新页面的几种常用方法
  18. 域适应(domain adaptation)
  19. 作曲 app android,文艺又好玩!安卓作曲达人App试用体验
  20. 【2022-09-14】米哈游秋招笔试三道编程题

热门文章

  1. mysql学习第一弹——查询基础
  2. 什么样的企业适合用 OKR?
  3. tomcat启动失败的3种解决办法
  4. Linux图形界面、文本界面切换方法
  5. 200页PPT:仓储物流装备大合集!
  6. 《淘宝店铺经营管理一册通》一一1.2 优化宝贝描述,让成交量飞涨
  7. golang二叉树的递归和非递归方式的前中后序遍历
  8. MATLAB正月点灯笼老师课程笔记:第四课:使用MATLAB制作简单的动画效果
  9. 【译Py】数据科学面试终极指南(四)
  10. c++的lvalue和rvalue以及引用