| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 面试 - 为什么foreach中不允许对元素进行add和remove -> 正文阅读 |
|
[Java知识库]面试 - 为什么foreach中不允许对元素进行add和remove |
1、foreach遍历ArrayList过程中使用?add 和 remove我们先来看看使用foreach遍历ArrayList过程中使用?add 和 remove 会出现什么样子的结果,然后再分析一下。
运行结果:
结果是出现了ConcurrentModificationException 异常,追踪下抛出异常的位置(ArrayList.java:911)
这个地方告诉我们如果 modCount 不等于 expectedModCount 的时候,就会抛出这个异常信息,那么这两个参数都代表了什么东西呢?为什么不相等的时候,就会出现异常呢? 2、追根溯源2.1、modCount是什么?这时候就要让我们去看源码了,在我们点到这个变量的时候,就会有注释告诉我们了 modCount 是 AbstractList 类中的一个成员变量,该值表示对List的修改次数。 这时候我们来看看 remove 方法中是否对这个变量进行了增减。 大家可以看到,在 remove 的方法中,实际上只是对 modCount 进行了++,那 expectedModCount 又是个什么东西呢? 2.2、expectedModCount 是什么?expectedModCount 是 ArrayList 中的一个内部类——Itr中的成员变量,我们来看下怎么又扯出个内部类Itr。 通过反编译可以发现foreach编译后内部是使用迭代器实现的。 ?迭代器是通过list.iterator()实例化的,list.iterator()就返回了一个内部类Itr的对象,从源码中可以看到Itr实现了Iterator接口,同时声明了expectedModCount这个成员变量,?expectedModCount 表示对ArrayList修改次数的期望值,它的初始值为 modCount。 2.3、熟悉的checkForComodification方法从源码可以看到这个类的next和remove方法里面都调用了一个checkForComodification方法,看到checkForComodification是不是很熟悉,这不就是异常的抛出位置吗。 checkForComodification方法是通过判断modCount和expectedModCount是否相等来决定是否抛出并发修改异常。 2.4、流程回顾通过查看编译后的class文件,可以看出大致流程如下:当j为3时,调用了remove方法,remove方法中修改了modCount值,然后再输出j值,再进入下一次循环,此时hasNext为true,进入循环体第一行代码,调用next方法,next方法再调用checkForComodification方法,然后发现expectedModCount和modCount不一致,最终抛出ConcurrentModificationException 异常。 ?也就是说,expectedModCount 初始化为 modCount 了,但是后面 expectedModCount 没有修改,而在 remove 和 add 的过程中修改了modCount ,这就导致了执行的时候,通过 checkForComodification 方法来判断两个值是否相等,如果相等了,那么没问题,如果不相等,那就给你抛出一个异常来。 而这也就是我们通俗说起来的 fail-fast 机制,也就是快速检测失败机制。 3、避免fail-fast 机制3.1、使用listIterator或iteratorfail-fast 机制也是可以避免的,比如再拿出来我们上面的代码
?这样的话,你就发现是可以运行的,也是没有问题的,我们看运行结果:
?结果也是显而易见的,我们实现了在 foreach 中进行 add 和 remove 的操作. 这里有个注意点,迭代器使用listIterator和iterator均可,看源码可以知道?listIterator其实使用的ListItr内部类,ListItr是继承了Itr类的,同时自己封了一些方法,例如add,hasPrevious,previous等等。所以代码中的remove方法是Itr类的,add方法是ListItr类的 ?listIterator和iterator区别:
3.2、使用CopyOnWriteArrayListCopyOnWriteArrayList 这个类也是能解决 fail-fast 的问题的,我们来试一下:
?运行结果:
CopyOnWriteArrayList实现了对这个元素中间进行移除添加的操作,那么他的内部源码是怎么实现的,实际上很简单,复制 也就是他创建一个新的数组,再将旧的数组复制到新的数组上,但是为什么很少有人推荐这种做法,根本原因还是?复制 因为你使用了复制,那么就一定会出现有两个存储相同内容的空间,这样消耗了空间,最后进行 GC 的时候,那是不是也需要一些时间去清理他,所以个人不是很推荐,但是写出来的必要还是有的。 3.2.1、CopyOnWriteArrayList的add方法
处理流程如下:
3.2.2、CopyOnWriteArrayList的remove方法
处理流程如下:
注意CopyOnWriteArrayList解决 fail-fast 的问题不是通过迭代器来remove或add元素的,而是通过list本身的remove和add方法,所以add的元素位置也不一样,迭代器是当前位置后面一个,CopyOnWriteArrayList是直接放到最后。 有想法的同学可以看看CopyOnWriteArrayList的listIterator和iterator,其实是一样的,都是返回的COWIterator内部类。 ?在COWIterator内部类中是不支持remove、set、add操作的,至少我使用的jdk1.8是不支持,会直接抛出UnsupportedOperationException异常: ?先写到这儿,后面有空再补充。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 23:51:36- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |