为什么不能在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() 方法允许在遍历时安全地删除当前元素,同时更新集合的结构和迭代器的状态,避免了上述问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/40210.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络基础:EIGRP

EIGRP&#xff08;Enhanced Interior Gateway Routing Protocol&#xff09;是由思科开发的一种高级距离矢量路由协议&#xff0c;结合了距离矢量和链路状态路由协议的优点&#xff1b;EIGRP具有快速收敛、高效带宽利用、负载均衡等特点&#xff0c;适用于各种规模的网络。EIGR…

python sklearn机械学习-数据预处理

&#x1f308;所属专栏&#xff1a;【机械学习】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您…

【设计模式】策略模式(定义 | 特点 | Demo入门讲解)

文章目录 定义策略模式的结构 QuickStart | DemoStep1 | 策略接口Step2 | 策略实现Step3 | 上下文服务类Step4 | 客户端 策略模式的特点优点缺点 定义 策略模式Strategy是一种行为模式&#xff0c;它能定义一系列算法&#xff0c;并将每种算法分别放入到独立的类中&#xff0c…

负载均衡类型和算法解析

假如你正在设计和开发一个分布式服务系统&#xff0c;系统中存在一批能够独立运行的服务&#xff0c;而在部署上也采用了集群模式以防止出现单点故障。所谓集群&#xff0c;就是指将多个服务实例集中在一起&#xff0c;对外提供同一业务功能&#xff0c;也就是任意请求都可以由…

吉利银河L6 AQS空气质量监控系统

结论 顶配才有AQS 开启空调且auto模式 则默认开启AQS 无法关闭AQS AQS的作用 银河L6 AQS触发 和 图标 AQS官方配置参数 官方文档 吉利用户手册

开源即正义,3D软件Blender设计指南

在当今数字化时代&#xff0c;开源软件的崛起不仅代表着技术的发展&#xff0c;更象征着一种信息自由和技术民主的理念。其本质是集众人之智&#xff0c;共同去完善一个软件&#xff0c;最终使双方互惠共赢。具体来说&#xff0c;开源的价值&#xff0c;在于打破资源垄断&#…

苹果AI的国产大模型之争,没有悬念

文 | 智能相对论 作者 | 陈泊丞 苹果终于公布了最新的AI进程。 一个月前&#xff0c;正如此前预期的那样&#xff0c;人工智能是今年 WWDC 发布会的焦点。全程105分钟的主题演讲&#xff0c;就有40多分钟用于介绍苹果的AI成果。 苹果似乎还有意玩了一把“谐音梗”&#xff…

用机器改变人类方向

1800 世纪初&#xff0c;美国迎来了工业革命&#xff0c;这是一个由技术进步推动的变革时代。新机器和制造技术的引入重塑了经济格局&#xff0c;提高了生产效率&#xff0c;同时减少了某些领域对手工劳动的需求。因此&#xff0c;这种转变导致了失业。 如今&#xff0c;我们看…

实现点击按钮导出页面pdf

在Vue 3 Vite项目中&#xff0c;你可以使用html2canvas和jspdf库来实现将页面某部分导出为PDF文档的功能。以下是一个简单的实现方式&#xff1a; 1.安装html2canvas和jspdf&#xff1a; pnpm install html2canvas jspdf 2.在Vue组件中使用这些库来实现导出功能&#xff1a;…

统计信号处理基础 习题解答11-11

题目 考虑矢量MAP估计量 证明这个估计量对于代价函数 使贝叶斯风险最小。其中&#xff1a;, &#xff0c;且. 解答 贝叶斯风险函数&#xff1a; 基于概率密度的非负特性&#xff0c;上述对积分要求最小&#xff0c;那就需要内层积分达到最小。令内层积分为&#xff1a; 上述积…

苹果Mac电脑能玩什么游戏 Mac怎么运行Windows游戏

相对于Windows平台来说&#xff0c;Mac电脑可玩的游戏较少。虽然苹果设备的性能足以支持各种大型游戏&#xff0c;但由于系统以及苹果配套服务的限制&#xff0c;很多游戏无法在Mac系统中运行。不过&#xff0c;借助虚拟机软件&#xff0c;Mac电脑可以突破系统限制玩更多的游戏…

光照老化试验箱在化工产品暴晒测试中的应用

概述 光照老化试验箱是一种模拟自然光照条件下材料老化情况的实验设备&#xff0c;广泛应用于化工、建材、电子、汽车等行业中对材料的耐候性、耐光性能等进行测试。通过模拟日光中的紫外线和温度等环境因素&#xff0c;加速材料老化过程&#xff0c;以此评估材料在长期使用中…

2024阿里云大模型自定义插件(如何调用自定义接口)

1&#xff0c;自定义插件入口 2&#xff0c;插件定义&#xff1a;描述插件的参数 2.1&#xff0c;注意事项&#xff1a; 2.1.1&#xff0c;只支持json格式的参数&#xff1b;只支持application/JSON&#xff1b;如下图&#xff1a; 2.1.2&#xff0c;需要把接口描述进行修改&a…

03:Spring MVC

文章目录 一&#xff1a;Spring MVC简介1&#xff1a;说说自己对于Spring MVC的了解&#xff1f;1.1&#xff1a;流程说明&#xff1a; 一&#xff1a;Spring MVC简介 Spring MVC就是一个MVC框架&#xff0c;Spring MVC annotation式的开发比Struts2方便&#xff0c;可以直接代…

LeetCode 算法:二叉搜索树中第K小的元素 c++

原题链接&#x1f517;&#xff1a;二叉搜索树中第K小的元素 难度&#xff1a;中等⭐️⭐️ 题目 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从1开始计数&#xff09;。 示例 1&#xff1a;…

第一周题目总结

1.车尔尼有一个数组 nums &#xff0c;它只包含 正 整数&#xff0c;所有正整数的数位长度都 相同 。 两个整数的 数位不同 指的是两个整数 相同 位置上不同数字的数目。 请车尔尼返回 nums 中 所有 整数对里&#xff0c;数位不同之和。 示例 1&#xff1a; 输入&#xff1a…

【嵌入式DIY实例-ESP8266篇】-LCD ST7735显示网络时间

LCD ST7735显示网络时间 文章目录 LCD ST7735显示网络时间1、硬件准备2、代码实现本文将介绍如何使用 ESP8266 NodeMCU Wi-Fi 板实现互联网时钟,其中时间和日期显示在 ST7735 TFT 显示屏上。 ST7735 TFT是一款分辨率为128160像素的彩色显示屏,采用SPI协议与主控设备通信。 1…

SQL语句(DML)

DML英文全称是Data Manipulation Language&#xff08;数据操作语言&#xff09;&#xff0c;用来对数据库中表的数据记录进行增删改等操作 DML-添加数据 insert into employee(id, workno, name, gender, age, idcard) values (1,1,Itcast,男,10,123456789012345678);select *…

AI 与数据的智能融合丨大模型时代下的存储系统

WOT 全球技术创新大会2024北京站于 6 月 22 日圆满落幕。本届大会以“智启新纪&#xff0c;慧创万物”为主题&#xff0c;邀请到 60 位不同行业的专家&#xff0c;聚焦 AIGC、领导力、研发效能、架构演进、大数据等热门技术话题进行分享。 近年来&#xff0c;数据和人工智能已…

记录搭建一台可域名访问的HTTPS服务器

一、背景 近期公司业务涉及到微信小程序&#xff0c;即将开发完成需要按照微信小程序平台的要求提供带证书的域名请求服务器。 资源背景介绍如下&#xff1a; 1、域名 公司已有一个二级域名&#xff0c;再次申请新的二级域名并且实现ICP备案不仅需要花重金重新购买&#xff0c;…