源码角度分析Java 循环中删除数据为什么会报异常

一、源码角度分析Java 循环中删除数据为什么会报异常

相信大家在之前或多或少都知道 Java 中在增强 for中删除数据会抛出:java.util.ConcurrentModificationException 异常,例如:如下所示程序:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (String l : list) {if (Objects.equals(l, "002") || Objects.equals(l,"003")) {list.remove(l);}}System.out.println(list);}
}

运行后会发现抛出了异常:

在这里插入图片描述

特别是一些新手小伙伴一不注意就陷入其中,当然解决方法也特别简单,可以转为迭代器,然后使用迭代器的 remove 方式删除数据,或者使用循环下标的方式通过下标进行删除,但需要注意正向循环和反向循环,如果是正向循环的话需要注意计算下标位置,不过不要担心,下面我们都会一一进行介绍。

首先来分析下为什么在增强 for 中会出现java.util.ConcurrentModificationException 异常,这里现将java编译成class形式,看增强 for最终是以何种形式执行的:

javac RmTest.java

编译后的内容如下:

public class RmTest {public RmTest() {}public static void main(String[] var0) {ArrayList var1 = new ArrayList();var1.add("001");var1.add("002");var1.add("003");var1.add("004");Iterator var2 = var1.iterator();while(true) {String var3;do {if (!var2.hasNext()) {System.out.println(var1);return;}var3 = (String)var2.next();} while(!Objects.equals(var3, "002") && !Objects.equals(var3, "003"));var1.remove(var3);}}
}

可以看到增强for最终是编译成迭代器的方式进行遍历数据,但需要注意的是删除数据依然使用的 List 中的 remove 方法,通过抛出的异常链可以看出,问题发生在了 next 方法中的 checkForComodification 方法下:

在这里插入图片描述

下面看到 ArrayList 下迭代器的 next 方法中,在 Itr 类下:

在这里插入图片描述
在这个方法中首先调用了 checkForComodification 方法,正好上面的异常链中也涉及到了 checkForComodification 方法,下面进到该方法中:

在这里插入图片描述
这里是不是看到了熟悉的 ConcurrentModificationException 异常,只要 modCountexpectedModCount 不相等就会抛出该异常,下面看下 expectedModCount 的声明位置:

在这里插入图片描述

在迭代器内部声明的,并且起始值等于 modCount,而 modCount 则在定义在 AbstractList 在迭代器的外部,这里还记得前面迭代器中使用的是 List 中的 remove 方法删除的数据,这里看到该方法中:

在这里插入图片描述
该方法实际的删除逻辑在 fastRemove 方法中,继续看到该方法下:

在这里插入图片描述
看到这里是不是很直观了,modCount 数值发生了变化,而迭代器中的expectedModCount 没有随之修改,就导致 expectedModCount != modCount 而抛出异常。

我们都知道使用迭代器中的 remove 方式是不会引发异常的,比如:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String l = iterator.next();if (Objects.equals(l, "002") || Objects.equals(l, "003")) {iterator.remove();}}System.out.println(list);}}

运行结果:

在这里插入图片描述

为什么迭代器的 remove 可以呢,下面看到该方法中:

在这里插入图片描述

可以看出迭代器的 remove 同样也是使用了 List 中的 remove 方法,但它会在删除后重置 expectedModCount 的值,使其保持和 modCount 一致,因此就不会触发上面的异常。

看到这里应该明白为什么会抛出异常了,但为什么这样设计呢?这里可以总结下其中,modCount主要表示集合被修改的次数,expectedModCount表示迭代器内部维护的集合被修改的次数。当modCountexpectedModCount不相等时,则表示肯定有其他某个地方对集合进行了修改,此时,如果继续使用迭代器遍历集合,就可能会出现遍历到非预期的元素或者下个元素不存在了,因此只要expectedModCountmodCount保持一致,数据就可认为是可信的。

通过这里也能给我们警醒,如果需要在并发情况下操作集合一定要选用线程安全的集合。

下面再补充下如果不用增强for,使用下标自增的方式删除是否可行吗?

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (int i = 0; i < list.size(); i++) {String l = list.get(i);if (Objects.equals(l, "002") || Objects.equals(l,"003")) {list.remove(i);}}System.out.println(list);}
}

运行后:

在这里插入图片描述

发现 003 并没有被移除,因为当移除了 002 后,002 后的数据顺势向前移位,原本003的下标为 2 ,移位后变成了 1 ,但下标 i 继续增长,便会错过后面的数据,那怎么解决呢,既然后面的数据向前移位,对下标i也向前移位就是了:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (int i = 0; i < list.size(); i++) {String l = list.get(i);if (Objects.equals(l, "002") || Objects.equals(l,"003")) {list.remove(i);i = i-1;}}System.out.println(list);}
}

运行后数据正常:

在这里插入图片描述

既然正向遍历下标需要移位,那如果反过来反向循环不就可以不管下标了吗:

public class RmTest {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("001");list.add("002");list.add("003");list.add("004");for (int i = list.size() - 1; i >= 0; i--) {String l = list.get(i);if (Objects.equals(l, "002") || Objects.equals(l, "003")) {list.remove(i);}}System.out.println(list);}
}

运行后数据正常:

在这里插入图片描述

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

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

相关文章

Django分页功能的使用和自定义分装

1. 在settings中进行注册 # drf配置 REST_FRAMEWORK {DEFAULT_AUTHENTICATION_CLASSES: (# rest_framework_jwt.authentication.JSONWebTokenAuthentication,rest_framework_simplejwt.authentication.JWTAuthentication,rest_framework.authentication.SessionAuthenticatio…

高等数学教材重难点题型总结(六)定积分的应用

本章全是物理题&#xff0c;总的来说计算层面并不难&#xff0c;难点主要在于&#xff1a;对于体积和面积的题&#xff0c;核心在于抽象出面积/体积元素——本质上就是被积函数&#xff1b;对于物理题&#xff0c;基础的物理的公式一定要牢记~ 1.求平面面积 2.求旋转体的体积…

爬虫批量下载科研论文(SciHub)

系列文章目录 利用 eutils 实现自动下载序列文件 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、获取文献信息二、下载文献PDF文件参考 前言 大家好✨&#xff0c;这里是bio&#x1f996;。…

初识《时间复杂度和空间复杂度》

目录 前言&#xff1a; 关于数据结构与算法 1.什么是数据结构&#xff1f; 2.什么是算法&#xff1f; 3.数据结构和算法的重要性 算法效率是什么&#xff1f; 1.怎样衡量一个算法的好坏呢&#xff1f; 2.算法的复杂度是个什么&#xff1f; 时间复杂度 1.时间复杂度的概…

微服务技术导学

文章目录 微服务结构认识微服务技术栈 微服务结构 技术&#xff1a; 解决异常定位&#xff1a; 持续集成&#xff0c;解决自动化的部署&#xff1a; 总结如下&#xff1a; 认识微服务 微服务演变&#xff1a; 技术栈 SpringCloud与SpringBoot版本对应关系

VS2022 C# 读取 excel 2023年

今天是2023年6月26日&#xff0c;我有一个excel表要读数据&#xff0c;然后放到winform程序来处理&#xff0c;网上的资料太旧&#xff0c;很多用不起来&#xff0c;试了一个可以使用&#xff0c;记录一下&#xff1a; 一、excel文件后缀需要小写。 二、用VS2022建一个winform…

Java练习题2020 -1

统计1到N的整数中&#xff0c;被A除余A-1的偶数的个数 输入说明&#xff1a;整数 N(N<10000), A, (A 输出说明&#xff1a;符合条件的数的个数 输入样例&#xff1a;10 3 输出样例&#xff1a;2 (说明&#xff1a;样例中符合条件的2个数是 2、8) import java.util.Scanner;p…

Web:探索 SpreadJS强大的在线电子表格库

1、概述 SpreadJS 是葡萄城结合 40 余年专业控件技术和在电子表格应用领域的经验而推出的纯前端表格控件,基于 HTML5,兼容 450 多种 Excel 公式,具备“高性能、跨平台、与 Excel 高度兼容”的产品特性,SpreadJS 在界面和功能上与 Excel 高度类似,但又不局限于 Excel,而是…

【电路笔记】-电路中的复数与相量(Phasor)

电路中的复数与相量(Phasor) 文章目录 电路中的复数与相量(Phasor)1、概述2、复数定义3、复数计算规则4、电子领域的复数5、总结 复数是一种重要的数学工具&#xff0c;广泛应用于包括电子学在内的许多物理领域。 这个概念可能看起来很奇怪&#xff0c;但它们的操作很简单&…

【Docker从入门到入土 6】Consul详解+Docker https安全认证(附证书申请方式)

Part 6 一、服务注册与发现的概念1.1 cmp问题1.2 服务注册与发现 二、Consul ----- 服务自动发现和注册2.1 简介2.2 为什么要用consul&#xff1f;2.3 consul的架构2.3 Consul-template 三、consul架构部署3.1 Consul服务器Step1 建立 Consul 服务Step2 查看集群信息Step3 通过…

axios封装以及详细用法

文章目录 axios用法(这里没有封装&#xff0c;下面有封装好的get&#xff0c;post方法&#xff0c;在axios封装里面)get &#xff0c;delete方法post&#xff0c;put方法 axios具体封装axios 具体参数配置 axios用法(这里没有封装&#xff0c;下面有封装好的get&#xff0c;pos…

项目中拖拽元素,可以使用html的draggable属性,当然也可以用第三方插件interact

项目中拖拽元素&#xff0c;可以使用html的draggable属性&#xff0c;当然也可以用第三方插件interact 一、安装二、引用三、使用 一、安装 npm install interactjs二、引用 import interact from interactjs三、使用 <div class"drag_box"> &…

基于android的 rk3399 同时支持多个USB摄像头

基于android的 rk3399 同时支持多个USB摄像头 一、前文二、CameraHal_Module.h三、CameraHal_Module.cpp四、编译&烧录Image五、App验证 一、前文 Android系统默认支持2个摄像头&#xff0c;一个前置摄像头&#xff0c;一个后置摄像头 需要支持数量更多的摄像头&#xff0…

selenium工作原理和反爬分析

一、 Selenium Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一&#xff0c;支持并行测试执行。Selenium通过使用特定于每种语言的驱动程序支持各种编程语言。Selenium支持的语言包括C#&#xff0c;Java&#xff0c;Perl&#xff0c;PHP&#xff0c;Python和Ru…

如何查看多开的逍遥模拟器的adb连接端口号

逍遥模拟器默认端口号为&#xff1a;21503。 不过&#xff0c;使用多开器多开的时候&#xff0c;端口就不一定是21503了。 如何查看&#xff1f; 进入G:\xiaoyao\Microvirt\MEmu\MemuHyperv VMs路径中 每多开一个模拟器&#xff0c;就会多出一个文件夹。 进入你要查找端口号…

2023年MathorCup高校数学建模挑战赛大数据挑战赛赛题浅析

比赛时长为期7天的妈杯大数据挑战赛如期开赛&#xff0c;为了帮助大家更好的选题&#xff0c;首先给大家带来赛题浅析&#xff0c;为了方便大家更好的选题。 赛道 A&#xff1a;基于计算机视觉的坑洼道路检测和识别 A题&#xff0c;图像处理类题目。这种题目的难度数模独一档…

SpringAOP源码解析之advice执行顺序(三)

上一章我们分析了Aspect中advice的排序为Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class&#xff0c;然后advice真正的执行顺序是什么&#xff1f;多个Aspect之间的执行顺序又是什么&#xff1f;就是我们本章探讨的问题。 准备工作 既…

基于Python Django 的微博舆论、微博情感分析可视化系统(V2.0)

文章目录 1 简介2 意义3 技术栈Django 4 效果图微博首页情感分析关键词分析热门评论舆情预测 5 推荐阅读 1 简介 基于Python的微博舆论分析&#xff0c;微博情感分析可视化系统&#xff0c;项目后端分爬虫模块、数据分析模块、数据存储模块、业务逻辑模块组成。 Python基于微博…

第八节——Vue渲染列表+key作用

一、列表渲染 vue中使用v-for指令进行列表 <template><div><!-- item 代表 当前循环的每一项 --><!-- index 代表 当前循环的下标--><!-- 注意&#xff1a;必须要加key--><div v-for"(item, index) in arr" :key"index"…

UE5 Blueprint发送http请求

一、下载插件HttpBlueprint、Json Blueprint Utilities两个插件是互相依赖的&#xff0c;启用&#xff0c;重启项目 目前两个是Beta的状态&#xff0c;如果你使用的平台支持就可以使用&#xff0c;我们的项目因为需要取Header的值&#xff0c;所有没法使用这两个插件&#xff0…