网站首页 > 资源文章 正文
在阅读《阿里巴巴Java开发手册》时,发现有一条关于在 foreach 循环里进行元素的 remove/add 操作的规约,具体内容如下:
错误演示
我们首先在 IDEA 中编写一个在 foreach 循环里进行 remove 操作的代码:
import java.util.ArrayList; import java.util.List; public class ForEachTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("wupx"); list.add("love"); list.add("huxy"); for (String temp : list) { if ("love".equals(temp)) { list.remove(temp); } } System.out.println(list); } }
此时执行代码,编译正确,执行成功!输出 [wupx, huxy]。
接着我们把 “love” 换成 “wupx” 或是 “huxy” 再来运行下,执行结果如下:
纳尼,居然报错了,为什么第一次运行没有报错呢?让我们一起来进行探讨吧!
追根溯源
为了研究为什么会出现这样的情况,我们可以根据异常堆栈信息,去追踪错误,其中涉及到的部分源码如下:
private class Itr implements Iterator<E> { int cursor; // 下一个要返回的元素的索引 int lastRet = -1; // 返回的最后一个元素的索引(如果没有返回-1) int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } 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++]); } cursor = i; lastRet = i - 1; checkForComodification(); } @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(); } }
从代码中可以看出,其实在集合遍历时维护一个初始值为 0 的游标 cursor,从头到尾地进行扫描,在 cursor==size 时,退出遍历。如下图所示,执行 remove 这个元素后,所有元素往前拷贝, size=size-1 即为2 ,这时 cursor 也等于 2。在执行 hasNext() 时, 结果为 false ,退出循环体,并没有机会执行到 next() 的第一行代码 checkForComodification() ,此方法用来判断 expectedModCount 和 modCount 是否相等, 如果不相等,则抛出
ConcurrentModificationException 异常。
之所以会报
ConcurrentModificationException 异常,是因为触发了 Java 的 fail-fast 机制,该机制是集合中比较常见的错误检测机制,通常出现在遍历集合元素的过程中。举个生活中的栗子:
比如上体育课时,在上课前都会依次报数,如果在报数期间,有人突然加进来,还要重新报数,再次报数,又有同学溜出去了,又要重新报数,这就是 fail-fast 机制,它是对集合(班级同学)遍历操作的错误检测机制,在遍历中途出现意料之外的修改时,通过 unchecked 异常反馈出来。这种机制经常出现在多线程环境下,当前线程会维护一个计数比较器(expectedModCount),记录已经修改的次数。在进入遍历前,会把实时修改次数 modCount 赋值给 expectedModCount,如果这两个数据不相等,则抛出异常。java.util 下的所有集合类都是 fail-fast。
不二法门
既然在 foreach 循环里进行元素的 remove/add 操作会有问题,那么我们可以使用手册中推荐的 Iterator 机制进行遍历时的删除或新增,代码如下:
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ForEachTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("wupx"); list.add("love"); list.add("huxy"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { if (iterator.next().equals("wupx")) { iterator.remove(); } } System.out.println(list); } }
如果是多线程并发,还需要在 Iterator 遍历时加锁,或者使用并发容器 CopyOnWriteArrayList 代替 ArrayList,该容器内部会对 Iterator 进行加锁操作。
总结
本文针对《阿里巴巴Java开发手册》中的强制要求不要在 foreach 循环里进行元素的 remove/add 操作出发,从源码层面来解释为什么,还用生活中的栗子来介绍 Java 中的 fail-fast 机制,因此在进行元素的 remove/add 操作时要用 Iterator 去遍历删除或新增。
读者福利:我把近一年经历过的Java岗位面试,和一些刷过的面试题都做成了PDF,PDF都是可以免费分享给大家的,只要关注私信我:【101】,就能获取免费领取方式!
猜你喜欢
- 2025-05-02 Windows11 a problem has been detected and windows怎么解决?
- 2025-05-02 模拟飞行 DCS F-14B Tomcat雄猫战斗机 中文指南 3.4警告指示灯
- 2025-05-02 不会用list的程序员不是好程序员,C++标准容器list类实例详解
- 2025-05-02 FTP删除文件夹时提示550 Remove directory operation failed
- 2025-05-02 随便说说removeFromSuperview方法
- 2025-05-02 Changzheng Hospital urges Novartis China to remove medical representatives amidst nationwide anti-corruption drive
- 2025-05-02 Ubuntu 系统安装NVIDIA 驱动(ubuntu 20.04安装nvidia驱动)
- 2025-05-02 自拍恶搞短片《How to Remove Your Mustache》
- 2025-05-02 usb safely remove v6.2.1.1284 使用7年还是那么好用分享给大家
- 2025-05-02 Futu and Tiger to remove apps from Chinese app stores
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- 电脑显示器花屏 (79)
- 403 forbidden (65)
- linux怎么查看系统版本 (54)
- 补码运算 (63)
- 缓存服务器 (61)
- 定时重启 (59)
- plsql developer (73)
- 对话框打开时命令无法执行 (61)
- excel数据透视表 (72)
- oracle认证 (56)
- 网页不能复制 (84)
- photoshop外挂滤镜 (58)
- 网页无法复制粘贴 (55)
- vmware workstation 7 1 3 (78)
- jdk 64位下载 (65)
- phpstudy 2013 (66)
- 卡通形象生成 (55)
- psd模板免费下载 (67)
- shift (58)
- localhost打不开 (58)
- 检测代理服务器设置 (55)
- frequency (66)
- indesign教程 (55)
- 运行命令大全 (61)
- ping exe (64)
本文暂时没有评论,来添加一个吧(●'◡'●)