为什么不能在foreach中删除元素
文章目录
- 快速失败机制(fail-fast)
 - for-each删除元素为什么报错
 - 原因分析
 - 逻辑分析
 
- 如何正确的删除元素
 - remove 后 break
 - for 循环
 - 使用 Iterator
 
- 总结
 
快速失败机制(fail-fast)
In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system’s state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.
这是快速失败机制的英文解释。翻译过来就是:系统设计中,“fail-fast”指的是一种策略,系统或模块被设计成在出现错误或失败时立即检测并报告。这种方法旨在通过停止正常操作而不是继续可能存在缺陷的过程来最小化失败的影响。fail-fast系统通常在操作的多个点检查系统状态,以便及早发现任何失败。fail-fast模块的责任是检测错误,然后让系统的更高级别处理它们。
这段话的大致意思就是,fail-fast 是一种通用的系统设计思想,一旦检测到可能会发生错误,就立马抛出异常,程序将不再往下执行
很多时候,我们会把 fail-fast 归类为 Java 集合框架的一种错误检测机制,但其实 fail-fast 并不是 Java 集合框架特有的机制
for-each删除元素为什么报错
下面这段代码:
List<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");for (String str : list) {if ("1".equals(str)) {list.remove(str);}}System.out.println(list);
 
在执行完之后就会报错
 
 看一下报错的原因是在checkForComodification这里报的错。下面是具体的代码
final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
 
也就是说,remove 的时候触发执行了 checkForComodification 方法,该方法对 modCount 和 expectedModCount 进行了比较,发现两者不等,就抛出了 ConcurrentModificationException 异常。
原因分析
为什么会执行checkForComodification 方法呢?是因为for-each的底层是迭代器Iterator配合while来实现的
List<String> list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
Iterator var2 = list.iterator();while(var2.hasNext()) {String str = (String)var2.next();if ("1".equals(str)) {list.remove(str);}
}System.out.println(list); 
看一下list的迭代器,点进iterator这个方法,发现它实现了Iterator接口

 再去看一下 Itr 这个类。
    private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;// prevent creating a synthetic constructorItr() {}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];}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();}}@Overridepublic void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);final int size = ArrayList.this.size;int i = cursor;if (i < size) {final Object[] es = elementData;if (i >= es.length)throw new ConcurrentModificationException();for (; i < size && modCount == expectedModCount; i++)action.accept(elementAt(es, i));// update once at end to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}}
 
也就是说 new Itr() 的时候 expectedModCount 被赋值为 modCount,而 modCount 是 ArrayList 中的一个计数器,用于记录 ArrayList 对象被修改的次数。ArrayList 的修改操作包括添加、删除、设置元素值等。每次对 ArrayList 进行修改操作时,modCount 的值会自增 1。
在迭代 ArrayList 时,如果迭代过程中发现 modCount 的值与迭代器的 expectedModCount 不一致,则说明 ArrayList 已被修改过,此时会抛出 ConcurrentModificationException 异常。这种机制可以保证迭代器在遍历 ArrayList 时,不会遗漏或重复元素,同时也可以在多线程环境下检测到并发修改问题。
逻辑分析
List<String> list = new ArrayList<>();list.add("1");list.add("2");list.add("3");for (String str : list) {if ("1".equals(str)) {list.remove(str);}}System.out.println(list);
 
由于 list 此前执行了 3 次 add 方法。
- add 方法调用 ensureCapacityInternal 方法
 - ensureCapacityInternal 方法调用ensureExplicitCapacity 方法
 - ensureExplicitCapacity 方法中会执行 modCount++
 
所以 modCount 的值在经过三次 add 后为 3,于是 new Itr() 后 expectedModCount 的值也为 3(回到前面去看一下 Itr 的源码)。
接着来执行 for-each 的循环遍历。
执行第一次循环时,发现“沉默王二”等于 str,于是执行 list.remove(str)。
- remove 方法调用 fastRemove 方法
 - fastRemove 方法中会执行 modCount++
 
modCount 的值变成了 4。
第二次遍历时,会执行 Itr 的 next 方法(String str = (String) var3.next();),next 方法就会调用 checkForComodification 方法。
此时 expectedModCount 为 3,modCount 为 4,就只好抛出 ConcurrentModificationException 异常了。
如何正确的删除元素
remove 后 break
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");for (String str : list) {if (1".equals(str)) {list.remove(str);break;}
} 
break 后循环就不再遍历了,意味着 Iterator 的 next 方法不再执行了,也就意味着 checkForComodification 方法不再执行了,所以异常也就不会抛出了。
但是呢,当 List 中有重复元素要删除的时候,break 就不合适了。
for 循环
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
for (int i = 0; i < list.size(); i++) {String str = list.get(i);if ("1".equals(str)) {list.remove(str);}
} 
for 循环虽然可以避开 fail-fast 保护机制,也就说 remove 元素后不再抛出异常;但是呢,这段程序在原则上是有问题的。为什么呢?
第一次循环的时候,i 为 0,list.size() 为 3,当执行完 remove 方法后,i 为 1,list.size() 却变成了 2,因为 list 的大小在 remove 后发生了变化,也就意味着“2”这个元素被跳过了。能明白吗?
remove 之前 list.get(1) 为“2”;但 remove 之后 list.get(1) 变成了“3”,而 list.get(0) 变成了“2”
使用 Iterator
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");Iterator<String> itr = list.iterator();while (itr.hasNext()) {String str = itr.next();if ("1".equals(str)) {itr.remove();}
} 
为什么使用 Iterator 的 remove 方法就可以避开 fail-fast 保护机制呢?看一下 remove 的源码就明白了。
public void remove() {if (lastRet < 0) // 如果没有上一个返回元素的索引,则抛出异常throw new IllegalStateException();checkForComodification(); // 检查 ArrayList 是否被修改过try {ArrayList.this.remove(lastRet); // 删除上一个返回元素cursor = lastRet; // 更新下一个元素的索引lastRet = -1; // 清空上一个返回元素的索引expectedModCount = modCount; // 更新 ArrayList 的修改次数} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException(); // 抛出异常}
} 
删除完会执行 expectedModCount = modCount,保证了 expectedModCount 与 modCount 的同步
总结
在使用 foreach 循环(或称 for-each 循环)遍历集合时,通常不能直接删除集合中的元素,原因如下:
Concurrent Modification Exception:
 当使用 foreach 循环遍历集合时,集合的结构不能被修改(例如添加或删除元素),否则会导致 ConcurrentModificationException 异常。这是因为 foreach 循环在背后使用迭代器来遍历集合,而迭代器在遍历时会维护一个 expected modCount(修改计数器),如果在遍历过程中修改了集合的结构,迭代器会检测到并抛出异常。
Invalidation of Iterator:
 删除元素后,集合的结构发生变化,这可能会使当前的迭代器失效。如果集合的结构发生了变化,迭代器可能无法正确遍历集合的剩余部分或者导致未定义行为。
Potential Logical Errors:
 直接在 foreach 循环内删除元素可能会导致逻辑错误。例如,如果不正确地更新迭代器或集合的大小,可能会导致遍历的元素不完整或错误。
为了安全地从集合中删除元素,应该使用迭代器的 remove() 方法。迭代器的 remove() 方法允许在遍历时安全地删除当前元素,同时更新集合的结构和迭代器的状态,避免了上述问题。
相关文章:
为什么不能在foreach中删除元素
文章目录 快速失败机制(fail-fast)for-each删除元素为什么报错原因分析逻辑分析 如何正确的删除元素remove 后 breakfor 循环使用 Iterator 总结 快速失败机制(fail-fast) In systems design, a fail-fast system is one which i…...
python学习-tuple及str
为什么需要元组 定义元组 元组的相关操作 元组的相关操作 - 注意事项 元组的特点 字符串 字符串的下标(索引) 同元组一样,字符串是一个:无法修改的数据容器。 如果必须要修改字符串,只能得到一个新的字符串ÿ…...
Python深度理解系列之【排序算法——冒泡排序】
读者大大们好呀!!!☀️☀️☀️ 👀期待大大的关注哦❗️❗️❗️ 🚀欢迎收看我的主页文章➡️木道寻的主页 文章目录 🔥前言🚀冒泡排序python实现算法实现图形化算法展示 ⭐️⭐️⭐️总结 🔥前…...
边界框在目标检测中的作用与应用
目标检测是计算机视觉领域的核心任务之一,旨在从图像或视频中识别和定位感兴趣的目标。边界框(Bounding Box)是目标检测中常用的一种表示方法,用于确定目标在图像中的确切位置。本文将详细探讨边界框的概念、它在目标检测中的角色…...
linux 环境报错:Peer reports incompatible or unsupported protocol version
出现问题的原因: curl 不兼容或不支持的协议版本。 解决方案: yum update -y nss curl libcurl如此继续之前的操作即可。...
深入解析:Java爬虫的本质是什么?
深入解析:Java爬虫的本质是什么? 引言: 随着互联网的快速发展,获取网络数据已成为许多应用场景中的重要需求。而爬虫作为一种自动化程序,能够模拟人类浏览器的行为,从网页中提取所需信息,成为了…...
【Matlab 六自由度机器人】机器人动力学之推导拉格朗日方程(附MATLAB机器人动力学拉格朗日方程推导代码)
【Matlab 六自由度机器人】机器人动力学概述 近期更新前言正文一、拉格朗日方程的推导1. 单自由度系统2. 单连杆机械臂系统3. 双连杆机械臂系统 二、MATLAB实例推导1. 机器人模型的建立2. 动力学代码 总结参考文献 近期更新 【汇总】 【Matlab 六自由度机器人】系列文章汇总 …...
线下生鲜蔬果店做小程序有什么方法
生鲜蔬果是生活所需,大小商家众多,零售批发各种经营模式,小摊贩或是超市门店都有着目标客户或准属性群体。竞争和获客转化也促进着商家寻找客源和加快线上进程。 尤其是以微信社交为主的私域场景,普客/会员都需要精细化管理营收和…...
几种linux开机自启脚本的方法
几种linux开机自启脚本的方法 1. 脚本添加到init.d目录中2. 创建服务service(推荐)3. /etc/profile & /etc/profile.d(不推荐)4. /etc/rc.local 本文以启动jenkins节点为例,需要持久连接,实现开机自启 …...
Qt开发笔记:Qt3D三维开发笔记(一):Qt3D三维开发基础概念介绍
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://blog.csdn.net/qq21497936/article/details/140059315 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、O…...
Firewalld 防火墙基础
Firewalld 防火墙基础 一、Firewalld概述firewalld 简介firewalld 和 iptables 的关系firewalld 与 iptables service 的区别 二、Firewalld 网络区域区域介绍Firewalld数据处理流程 三、Firewalld 防火墙的配置方法firewall-config 图形工具“区域”选项卡“服务”选项卡改变防…...
针对 Windows 10 的功能更新,版本 22H2 - 错误 0xc1900204
最近想帮女朋友生win11发现她电脑安装更新总是卡到安装%10这里失败 原来是安装路径被修改过了,改回c盘 win R → 输入regedit 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion...
goframe框架规范限制(but it should be named with “Res“ suffix like “XxxRes“)
背景: 首页公司最近要启动一个项目,公司主要业务是用java开发的,但是目前这个方向的项目,公司要求部署在主机上,就是普通的一台电脑上,电脑配置不详,还有经常开关机,所以用java面临…...
格式化选NTFS还是exFAT 格式化NTFS后Mac不能用怎么办 移动硬盘格式化ntfs和exfat的区别
面对硬盘、U盘或移动硬盘的格式化决策,NTFS与exFAT作为主流的文件系统,用户在选择时可以根据它们的不同特点来选择适用场景。下面我们来看看格式化选NTFS还是exFAT,格式化NTFS后Mac不能用怎么办的相关内容。 一、格式化选NTFS还是exFAT 在数…...
中国桥梁空间分布数据
2020年中国桥梁空间分布数据,共包含102000余条数据。 数据属性表包括:地级市名、区县名、桥梁名称和经纬度。有shp和EXCEl两种格式数据。目前暂没有广西、广东和台湾三个省份数据。...
14-15 为什么我们现在对阅读如此难以接受
写出来感觉很奇怪,但最近我感觉自己失去了阅读能力。长篇文本对我来说尤其具有挑战性。句子很难读完。更别提章节了。章节有很多段落,而段落又由许多句子组成。 啊。 即使在极少数情况下,我读完了一章,下一页上已经有另一章等着…...
经典的卷积神经网络模型 - ResNet
经典的卷积神经网络模型 - ResNet flyfish 2015年,何恺明(Kaiming He)等人在论文《Deep Residual Learning for Image Recognition》中提出了ResNet(Residual Network,残差网络)。在当时,随着…...
【Git 学习笔记】1.3 Git 的三个阶段
1.3 Git 的三个阶段 由于远程代码库后续存在新的提交,因此实操过程中的结果与书中并不完全一致。根据书中 HEAD 指向的 SHA-1:34acc370b4d6ae53f051255680feaefaf7f7850d,可通过以下命令切换到对应版本,并新建一个 newdemo 分支来…...
华为DCN之:SDN和NFV
1. SDN概述 1.1 SDN的起源 SDN(Software Defined Network)即软件定义网络。是由斯坦福大学Clean Slate研究组提出的一种新型网络创新架构。其核心理念通过将网络设备控制平面与数据平面分离,从而实现了网络控制平面的集中控制,为…...
黑马头条-数据管理平台
目录 项目准备 验证码登录 验证码登录-流程 token 的介绍 个人信息设置和 axios 请求拦截器 axios 响应拦截器和身份验证失败 优化-axios 响应结果 发布文章-富文本编辑器 项目准备 技术: • 基于 Bootstrap 搭建网站标签和样式 • 集成 wangEditor 插件…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
