当前位置: 首页 > news >正文

为什么不能在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中删除元素

文章目录 快速失败机制&#xff08;fail-fast&#xff09;for-each删除元素为什么报错原因分析逻辑分析 如何正确的删除元素remove 后 breakfor 循环使用 Iterator 总结 快速失败机制&#xff08;fail-fast&#xff09; In systems design, a fail-fast system is one which i…...

python学习-tuple及str

为什么需要元组 定义元组 元组的相关操作 元组的相关操作 - 注意事项 元组的特点 字符串 字符串的下标&#xff08;索引&#xff09; 同元组一样&#xff0c;字符串是一个&#xff1a;无法修改的数据容器。 如果必须要修改字符串&#xff0c;只能得到一个新的字符串&#xff…...

Python深度理解系列之【排序算法——冒泡排序】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;冒泡排序python实现算法实现图形化算法展示 ⭐️⭐️⭐️总结 &#x1f525;前…...

边界框在目标检测中的作用与应用

目标检测是计算机视觉领域的核心任务之一&#xff0c;旨在从图像或视频中识别和定位感兴趣的目标。边界框&#xff08;Bounding Box&#xff09;是目标检测中常用的一种表示方法&#xff0c;用于确定目标在图像中的确切位置。本文将详细探讨边界框的概念、它在目标检测中的角色…...

linux 环境报错:Peer reports incompatible or unsupported protocol version

出现问题的原因&#xff1a; curl 不兼容或不支持的协议版本。 解决方案&#xff1a; yum update -y nss curl libcurl如此继续之前的操作即可。...

深入解析:Java爬虫的本质是什么?

深入解析&#xff1a;Java爬虫的本质是什么&#xff1f; 引言&#xff1a; 随着互联网的快速发展&#xff0c;获取网络数据已成为许多应用场景中的重要需求。而爬虫作为一种自动化程序&#xff0c;能够模拟人类浏览器的行为&#xff0c;从网页中提取所需信息&#xff0c;成为了…...

【Matlab 六自由度机器人】机器人动力学之推导拉格朗日方程(附MATLAB机器人动力学拉格朗日方程推导代码)

【Matlab 六自由度机器人】机器人动力学概述 近期更新前言正文一、拉格朗日方程的推导1. 单自由度系统2. 单连杆机械臂系统3. 双连杆机械臂系统 二、MATLAB实例推导1. 机器人模型的建立2. 动力学代码 总结参考文献 近期更新 【汇总】 【Matlab 六自由度机器人】系列文章汇总 …...

线下生鲜蔬果店做小程序有什么方法

生鲜蔬果是生活所需&#xff0c;大小商家众多&#xff0c;零售批发各种经营模式&#xff0c;小摊贩或是超市门店都有着目标客户或准属性群体。竞争和获客转化也促进着商家寻找客源和加快线上进程。 尤其是以微信社交为主的私域场景&#xff0c;普客/会员都需要精细化管理营收和…...

几种linux开机自启脚本的方法

几种linux开机自启脚本的方法 1. 脚本添加到init.d目录中2. 创建服务service&#xff08;推荐&#xff09;3. /etc/profile & /etc/profile.d&#xff08;不推荐&#xff09;4. /etc/rc.local 本文以启动jenkins节点为例&#xff0c;需要持久连接&#xff0c;实现开机自启 …...

Qt开发笔记:Qt3D三维开发笔记(一):Qt3D三维开发基础概念介绍

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/140059315 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、O…...

Firewalld 防火墙基础

Firewalld 防火墙基础 一、Firewalld概述firewalld 简介firewalld 和 iptables 的关系firewalld 与 iptables service 的区别 二、Firewalld 网络区域区域介绍Firewalld数据处理流程 三、Firewalld 防火墙的配置方法firewall-config 图形工具“区域”选项卡“服务”选项卡改变防…...

针对 Windows 10 的功能更新,版本 22H2 - 错误 0xc1900204

最近想帮女朋友生win11发现她电脑安装更新总是卡到安装%10这里失败 原来是安装路径被修改过了&#xff0c;改回c盘 win R → 输入regedit 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion...

goframe框架规范限制(but it should be named with “Res“ suffix like “XxxRes“)

背景&#xff1a; 首页公司最近要启动一个项目&#xff0c;公司主要业务是用java开发的&#xff0c;但是目前这个方向的项目&#xff0c;公司要求部署在主机上&#xff0c;就是普通的一台电脑上&#xff0c;电脑配置不详&#xff0c;还有经常开关机&#xff0c;所以用java面临…...

格式化选NTFS还是exFAT 格式化NTFS后Mac不能用怎么办 移动硬盘格式化ntfs和exfat的区别

面对硬盘、U盘或移动硬盘的格式化决策&#xff0c;NTFS与exFAT作为主流的文件系统&#xff0c;用户在选择时可以根据它们的不同特点来选择适用场景。下面我们来看看格式化选NTFS还是exFAT&#xff0c;格式化NTFS后Mac不能用怎么办的相关内容。 一、格式化选NTFS还是exFAT 在数…...

中国桥梁空间分布数据

2020年中国桥梁空间分布数据&#xff0c;共包含102000余条数据。 数据属性表包括&#xff1a;地级市名、区县名、桥梁名称和经纬度。有shp和EXCEl两种格式数据。目前暂没有广西、广东和台湾三个省份数据。...

14-15 为什么我们现在对阅读如此难以接受

写出来感觉很奇怪&#xff0c;但最近我感觉自己失去了阅读能力。长篇文本对我来说尤其具有挑战性。句子很难读完。更别提章节了。章节有很多段落&#xff0c;而段落又由许多句子组成。 啊。 即使在极少数情况下&#xff0c;我读完了一章&#xff0c;下一页上已经有另一章等着…...

经典的卷积神经网络模型 - ResNet

经典的卷积神经网络模型 - ResNet flyfish 2015年&#xff0c;何恺明&#xff08;Kaiming He&#xff09;等人在论文《Deep Residual Learning for Image Recognition》中提出了ResNet&#xff08;Residual Network&#xff0c;残差网络&#xff09;。在当时&#xff0c;随着…...

【Git 学习笔记】1.3 Git 的三个阶段

1.3 Git 的三个阶段 由于远程代码库后续存在新的提交&#xff0c;因此实操过程中的结果与书中并不完全一致。根据书中 HEAD 指向的 SHA-1&#xff1a;34acc370b4d6ae53f051255680feaefaf7f7850d&#xff0c;可通过以下命令切换到对应版本&#xff0c;并新建一个 newdemo 分支来…...

华为DCN之:SDN和NFV

1. SDN概述 1.1 SDN的起源 SDN&#xff08;Software Defined Network&#xff09;即软件定义网络。是由斯坦福大学Clean Slate研究组提出的一种新型网络创新架构。其核心理念通过将网络设备控制平面与数据平面分离&#xff0c;从而实现了网络控制平面的集中控制&#xff0c;为…...

黑马头条-数据管理平台

目录 项目准备 验证码登录 验证码登录-流程 token 的介绍 个人信息设置和 axios 请求拦截器 axios 响应拦截器和身份验证失败 优化-axios 响应结果 发布文章-富文本编辑器 项目准备 技术&#xff1a; • 基于 Bootstrap 搭建网站标签和样式 • 集成 wangEditor 插件…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

认识CMake并使用CMake构建自己的第一个项目

1.CMake的作用和优势 跨平台支持&#xff1a;CMake支持多种操作系统和编译器&#xff0c;使用同一份构建配置可以在不同的环境中使用 简化配置&#xff1a;通过CMakeLists.txt文件&#xff0c;用户可以定义项目结构、依赖项、编译选项等&#xff0c;无需手动编写复杂的构建脚本…...