来源 | 公众号 我是程序汪

阿粉的读者遇到了一个比较经典的面试题,也就是标题上说的,为什么 foreach 中不允许对元素进行 add 和 remove。阿粉就这个问题深入分析一下为什么不让使用 add 和 remove,并且实际运行一下,我们来看一下。

ArrayList

我们先来看看 ArrayList 中如果我们使用了 add 和 remove 会出现什么样子的结果,然后我们分析一下。

public static void main(String[] args) {List<String> list = new ArrayList<>();//把元素放到list里面去for (int i = 0 ; i < 10 ; i++ ) {list.add(i + "");}for (String s: list) {if ("5".equals(s)){list.remove(5);}System.out.println(s);}}

我们先看看结果是什么样子的。

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)at java.util.ArrayList$Itr.next(ArrayList.java:861)

这时候就有人说,你为啥不直接用 iterator 迭代器遍历呢?其实说这话的,一般都是没去看过源码的,为什么这么说,如果你要是反编译出来 foreach 这一段代码,那么你肯定发现内部是使用迭代器实现的,既然这样,那好,我们再用迭代器遍历一下试试。

如果您正在学习Spring Boot,推荐一个连载多年还在继续更新的免费教程:http://blog.didispace.com/spring-boot-learning-2x/

public static void main(String[] args) {List<Integer> list = new ArrayList<>();//把元素放到list里面去for (int i = 0 ; i < 10 ; i++ ) {list.add(i);}Iterator<Integer> iterator = list.iterator();while(iterator.hasNext()){Integer integer = iterator.next();if(integer==5){list.remove();   //注意这个地方}}}

那结果如何呢?结果是一样的,还是会有异常的出现。

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)at java.util.ArrayList$Itr.next(ArrayList.java:861)

都出现了相同的异常 ConcurrentModificationException ,既然它已经给我们提示出异常的位置了,那么我们就来看看 ArrayList 的源码中,是什么样子的。

异常位置

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

这个地方告诉我们如果 modCount 不等于 expectedModCount 的时候,就会抛出这个异常信息,那么这两个参数都代表了什么东西呢?为什么不相等的时候,就会出现异常呢?

这时候就要让我们去看源码了在我们点到这个变量的时候,就会有注释告诉我们了 modCount 是 AbstractList 类中的一个成员变量,该值表示对List的修改次数

这时候我们来看看 remove 方法中是否对这个变量进行了增减。

public E remove(int index) {rangeCheck(index); //检查index是否合法modCount++; //modCout直接++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 workreturn 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; // 设置为null方便GC}

大家可以看到,在 remove 的方法中,实际上只是对 modCount 进行了++,那 expectedModCount 又是个什么东西呢?

通过remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。

expectedModCount 是 ArrayList 中的一个内部类——Itr中的成员变量。

我们来找找源码。

int expectedModCount = modCount;

而 expectedModCount 表示对ArrayList修改次数的期望值,它的初始值为 modCount。

我们看一下 ArrayList 中的内部类是怎么给他赋值了,毕竟他的初始值是 modCount,而这个内部类就是 iterator 。

从源码可以看到这个类的next和remove方法里面都调用了一个checkForComodification方法,他是通过判断modCount和expectedModCount是否相等来决定是否抛出并发修改异常.

final int expectedModCount = modCount;

也就是说,expectedModCount 初始化为 modCount 了,但是后面 expectedModCount 他没有修改呀,而在 remove 和 add 的过程中 modCount 是进行了修改了的,这就导致了如果执行的时候,他就会通过 checkForComodification 方法来判断两个是否相等,如果相等了,那么没问题,如果不相等,那就给你抛出一个异常来。

而这也就是我们通俗说起来的 fail-fast 机制,也就是快速检测失败机制。

而这种 fail-fast 机制也是可以避免的,比如再拿出来我们上面的代码,

public static void main(String[] args) {List<Integer> list = new ArrayList<>();//把元素放到list里面去for (int i = 0 ; i < 10 ; i++ ) {list.add(i);}System.out.print("没有删除元素前"+list.toString());Iterator<Integer> iterator = list.iterator();while(iterator.hasNext()){Integer integer = iterator.next();if(integer==5){iterator.remove();   //注意这个地方}}System.out.print("删除元素后"+list.toString());}

这样的话,你就发现是可以运行的,也是没有问题的,我们看运行结果:

没有删除元素前[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]删除元素后[0, 1, 2, 3, 4, 6, 7, 8, 9]

结果也是显而易见的,我们实现了在 foreach 中进行 add 和 remove 的操作.

其实还有一种方式 那就是 CopyOnWriteArrayList ,这个类也是能解决 fail-fast 的问题的,我们来试一下,

public static void main(String[] args) {CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();//把元素放到list里面去for (int i = 0 ; i < 10 ; i++ ) {list.add(i);}System.out.print("没有删除元素前"+list.toString());Iterator<Integer> iterator = list.iterator();while(iterator.hasNext()){Integer integer = iterator.next();if(integer==5){list.remove(5);   //注意这个地方}}System.out.print("删除元素后"+list.toString());}

我们运行后结果是一样的,

没有删除元素前[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]删除元素后[0, 1, 2, 3, 4, 6, 7, 8, 9]

另外,如果您正在学习Spring Cloud,推荐一个连载多年还在继续更新的免费教程:https://blog.didispace.com/spring-cloud-learning/

他实现了对这个元素中间进行移除的操作,那么他的内部源码是怎么实现的,实际上很简单,复制

也就是他创建一个新的数组,再将旧的数组复制到新的数组上,但是为什么很少有人推荐这种做法,根本原因还是 复制

因为你使用了复制,那么就一定会出现有两个存储相同内容的空间,这样消耗了空间,最后进行 GC 的时候,那是不是也需要一些时间去清理他,所以阿粉个人不是很推荐,但是写出来的必要还是有的,毕竟是个人建议,各位看官看是不是会如何在 foreach 中去 remove了?

往期推荐

支付宝员工因绩效3.25B被辞退,员工告上法院,结果来了!

为什么 JSP 还没有被淘汰?

理工男有多香?一张桌子、一条视频,股价狂涨13.51%!

JWT 和 JJWT,别再傻傻分不清了!

Spring Boot + MyBatis + MySQL 实现读写分离!

技术交流群

最近有很多人问,有没有读者交流群,想知道怎么加入。加入方式很简单,有兴趣的同学,只需要点击下方卡片,回复“加群“,即可免费加入我们的高质量技术交流群!

点击阅读原文,送你免费Spring Boot教程!

面试:为什么foreach中不允许对元素进行add和remove相关推荐

  1. 面试 - 为什么foreach中不允许对元素进行add和remove

    1.foreach遍历ArrayList过程中使用 add 和 remove 我们先来看看使用foreach遍历ArrayList过程中使用 add 和 remove 会出现什么样子的结果,然后再分析 ...

  2. foreach去除重复元素java_Java foreach 中List移除元素抛出ConcurrentModificationException原因全解析...

    本文重点探讨 foreach 循环中List 移除元素造成 java.util.ConcurrentModificationException 异常的原因. 先看<阿里巴巴 Java开发手册&g ...

  3. C# - 在foreach中删除元素

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 在for ...

  4. 指针增量和数组的关系,指针偏移的补充,(重要面试),gdp调试,将数组中的n个元素逆序存放

    1.指针增量和数组的关系 //加1  代表了地址偏移了一个类型的字节数(整形数偏移四个字节,char形数偏移了一个字节) 再来下标法: 2.指针偏移的补充 也可以换一种写法(第12行) 但是呢同样的代 ...

  5. java list 遍历 删除元素_java中List遍历删除元素相关做法和注意事项

    首先,使用简单的for循环时,list.remove()肯定是没问题的,只要注意一下下标别越界就行. 示例代码: List ll = new ArrayList(); ll.add("1&q ...

  6. Java循环中删除一个列表元素

    本文主要想讲述一下我对之前看到一篇文章的说法.假设跟你的想法有出入,欢迎留言.一起讨论. #3. 在循环中删除一个列表元素 考虑以下的代码.迭代过程中删除元素: ArrayList<String ...

  7. Java forEach中 Lambda Expr中的 final变量要求

    https://my.oschina.net/wadelau/blog/1859419 Java forEach中 Lambda Expr中的 final变量要求 Java8闭包 闭包是一个函数在创建 ...

  8. java 查找链表中间元素_如何在Java中一次性查找Java中链表的中间元素

    如何在一次传递中找到LinkedList的中间元素?这是一个 Java 和非Java程序员面试时经常被问到的编程问题.这个问题类似于检查回文或计算阶乘,有时也会要求编写代码.为了回答这个问题,候选人必 ...

  9. php遍历中记录所有,php遍历类中包含的所有元素的方法

    这篇文章主要介绍了php遍历类中包含的所有元素的方法,涉及php中getConstants方法及数组操作的相关技巧,需要的朋友可以参考下 本文实例讲述了php遍历类中包含的所有元素的方法.分享给大家供 ...

最新文章

  1. 百度短视频推荐系统的目标设计
  2. 如何在Windows7上安装Hyper-v manager
  3. 通过反射获取子类和父类定义的属性
  4. 数据倾斜的原因和解决方案
  5. HDU 1225 覆盖的面积
  6. NO.1 python_人工智能_学习路线
  7. vue大括号里接受一个函数_vue源码探究(第四弹)
  8. 设计趋势|几何元素增加Banner版面率
  9. client心跳 websocket_理解websocket的原理
  10. 如何使用ssh工具便于远程管理
  11. google font 字体下载方式
  12. 盘点那些不为大众所知,却暗地里很“牛逼”的软件
  13. 『深度应用』首届中国心电智能大赛复赛开源(第三十一名,得分0.841484)
  14. 微信公众号调取相册和摄像头功能,实现图片上传
  15. html 图片滚动 放大缩小,js实现图片旋转 js滚动鼠标中间对图片放大缩小
  16. HarmonyOS助力构建“食用菌智慧农场”
  17. 金融与python-基于Python的金融分析与风险管理
  18. 成人学习国画可能产生哪些错误的思想?
  19. fc天使之翼2020修改版下载_(安卓游戏)闲置超市大亨无限金钱版、模拟经营——安卓游戏下载资讯攻略信息...
  20. 超酷! Atlas给黑白视频“上色”

热门文章

  1. C语言自学《四》---- 循 环
  2. oracle监听器启动错误-TNS-12546: TNS:permission denied
  3. linux 查看磁盘分区的文件系统格式
  4. apache activemq ActiveMQ 修改密码
  5. metasploit快速入门(二)收集信息
  6. mysql 查看版本
  7. Linux下的权限掩码umask
  8. windows api打开文件对话框
  9. 遍历系统的所有ObjectType和TypeIndex
  10. 数组排序方法及C实现的总结