for-each循环陷阱
for-each删除元素报错
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("haha");
list.add("xixi");
list.add("hehe");
for (String s : list) {
if ("haha".equals(s))
list.remove(s);
}
System.out.println(list);
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at test.foreachTest.Test.main(Test.java:13)
remove 的时候触发执行了
checkForComodification方法,该方法对 modCount 和 expectedModCount 进行了比较,发现两者不等,就抛出了ConcurrentModificationException异常。ArrayList重写了Iterable的iterator方法
public Iterator<E> iterator() {
return new Itr();
}
// 计数器,用于记录 ArrayList 对象被修改的次数。ArrayList 的修改操作包括添加、删除、设置元素值等。每次对 ArrayList 进行修改操作时,modCount 的值会自增 1。
protected transient int modCount = 0;
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
// new Itr() 的时候 expectedModCount 被赋值为 modCount
int expectedModCount = modCount;
Itr() {}
// 判断是否还有下个元素
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
// 获取下个元素
public E next() {
// 检查 ArrayList 是否被修改过
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();
}
}
- fail-fast 是一种通用的系统设计思想,一旦检测到可能会发生错误,就立马抛出异常,程序将不再往下执行。
- 在迭代 ArrayList 时,如果迭代过程中发现 modCount 的值与迭代器的 expectedModCount 不一致,则说明 ArrayList 已被修改过,此时会抛出 ConcurrentModificationException 异常。这种机制可以保证迭代器在遍历 ArrayList 时,不会遗漏或重复元素,同时也可以在多线程环境下检测到并发修改问题。
执行逻辑
list执行3次add,每次add都会调用 ensureCapacityInternal 方法,ensureCapacityInternal 方法调用 ensureExplicitCapacity 方法,ensureExplicitCapacity 方法中会执行
modCount++。三次add后modCount为3第一次遍历时,执行remove,remove 方法调用 fastRemove 方法,fastRemove 方法中会执行
modCount++,modCound变成4第二次遍历时,会执行 Itr 的 next 方法,next 方法就会调用
checkForComodification方法。此时 expectedModCount 为 3,modCount 为 4,抛出 ConcurrentModificationException 异常。
正确删除元素
remove后break
// 没法删除多个重复元素
for (String s : list) {
if ("haha".equals(s)) {
list.remove(s);
break;
}
}
for循环
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if ("haha".equals(s)) {
// 删除后,size减一,list中后一个元素会移到被删除的下标i处
// 但下次循环不会再遍历下标i处的元素了
list.remove(s);
}
}
Iterator自带的remove()
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String s = itr.next();
if ("haha".equals(s))
itr.remove();
}
- ArrayList中的内部类Itr
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
...
public E next() {
...
// 记录这次调用next返回的元素
return (E) elementData[lastRet = i];
}
public void remove() {
// 如果没有上一个返回元素的索引,则抛出异常
if (lastRet < 0)
throw new IllegalStateException();
// 检查 ArrayList 是否被修改过
checkForComodification();
try {
// 删除上一个返回元素,也就是上次调用next返回的元素
ArrayList.this.remove(lastRet);
// 更新下一个元素的索引
cursor = lastRet;
// 清空上一个返回元素的索引
lastRet = -1;
// 更新 ArrayList 的修改次数,保证了 expectedModCount 与 modCount 的同步
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
...
}
流
- 采用 Stream 流的filter() 方法来过滤集合中的元素,然后再通过 collect() 方法将过滤后的元素收集到一个新的集合中。
List<String> list = new ArrayList<>(Arrays.asList("haha", "xixi", "hehe"));
list = list.stream().filter(s -> !s.equals("haha")).collect(Collectors.toList());
总结
- 之所以不能在foreach里执行删除操作,是因为foreach 循环是基于迭代器实现的,而迭代器在遍历集合时会维护一个 expectedModCount 属性来记录集合被修改的次数。如果在 foreach 循环中执行删除操作会导致 expectedModCount 属性值与实际的 modCount 属性值不一致,从而导致迭代器的 hasNext() 和 next() 方法抛出 ConcurrentModificationException 异常。
- 为了避免这种情况,应该使用迭代器的 remove() 方法来删除元素,该方法会在删除元素后更新迭代器状态,确保循环的正确性。如果需要在循环中删除元素,应该使用迭代器的 remove() 方法,而不是集合自身的 remove() 方法。
for-each循环陷阱的更多相关文章
- NodeJS中的循环陷阱
Node.js的异步机制由事件和回调函数实现,一開始接触可能会感觉违反常规,但习惯以后就会发现还是非常easy的. 然而这之中事实上暗藏不少陷阱.一个非常easy遇到的问题就是回到循环的回调函数. e ...
- JavaScript中for..in循环陷阱介绍
for...in循环中的循环计数器是字符串,而不是数字它包含当前属性的名称或当前数组元素的索引,下面有个不错的示例大家可以参考下 大家都知道在JavaScript中提供了两种方式迭代对象: (1) ...
- JS中for...in循环陷阱及遍历数组的方式对比
JavaScript中有很多遍历数组的方式,比较常见的是for(var i=0;i<arr.length;i++){},以及for...in...循环等,这些遍历都有各自的优缺点,下面来看看各种 ...
- 你不知道的JS之作用域和闭包(五)作用域闭包
原文:你不知道的js系列 一个简单粗暴的定义 闭包就是即使一个函数在它所在的词法作用域外部被执行,这个函数依然可以访问这个作用域. 比如: function foo() { var a = 2; fu ...
- python-__getattr__ 和 __getattribute__
python3完全使用了新式类,废弃了旧式类,getattribute作为新式类的一个特性有非常奇妙的作用.查看一些博客和文章后,发现想要彻底理解getattr和getattribute的区别,实际上 ...
- python __getattribute__、__getattr__、__setattr__详解
__getattribute__ 官方文档中描述如下: 该方法可以拦截对对象属性的所有访问企图,当属性被访问时,自动调用该方法(只适用于新式类).因此常用于实现一些访问某属性时执行一段代码的特性. 需 ...
- js for in
JavaScript中for..in循环陷阱 大家都知道在JavaScript中提供了两种方式迭代对象: (1)for 循环: (2)for..in循环: 使用for循环进行迭代数组对象,想必 ...
- js mvvm:闲来无事,实现一个只具最基本数据双向绑定的mvvm
近期项目内用knockoutjs. 想模拟实现数据双向绑定的基本功能. 只具有最基本的功能,且很多细节未曾考虑,也未优化精简. 能想到的缺少的模块 1事件监听,自定义访问器事件 2模版 3父子级 编码 ...
- 你认识的C# foreach语法糖,真的是全部吗?
本文的知识点其实由golang知名的for循环陷阱发散而来, 对应到我的主力语言C#, 其实牵涉到闭包.foreach.为了便于理解,我重新组织了语言,以倒叙结构行文. 先给大家提炼出一个C#题:观察 ...
- .NET周报【11月第4期 2022-11-30】
国内文章 .NET 7 的 AOT 到底能不能扛反编译? https://www.cnblogs.com/huangxincheng/p/16917197.html 在B站,公众号上发了一篇 AOT ...
随机推荐
- python网络连接报错:ValueError("Unable to determine SOCKS version from %s" % proxy_url) ValueError: Unable to determine SOCKS version from socks://192.168.1.100:1080/
python应用proxy网络连接报错: return super().send(request, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...
- 惠普暗影精灵2pro挑电池和电源适配器
自己几年前买的暗影2pro前几个月坏掉了,无法充电,而且偶发性掉电,经过长时间研究发现该款电脑存在挑电池和电源适配器的问题. 相关资料: https://www.chinafix.com/thread ...
- pytorch中神经网络的多线程数设置:torch.set_num_threads(N)
实验室的同学一直都是在服务器上既用CPU训练神经网络也有使用GPU的,最近才发现原来在pytorch中可以通过设置 torch.set_num_threads(args.thread) 来限制CP ...
- vim跳转到上次和下次光标位置
在vim的命令模式下: ctrl + i 下次光标位置; ctrl + o 上次光标位置. =====================================
- 【转载】 【Java分享客栈】我曾经的两个Java老师一个找不到工作了一个被迫转行了
本文作者: 福隆苑居士 本文链接: https://www.cnblogs.com/fulongyuanjushi/p/16182465.html 版权声明: 本博客所有文章除特别声明外,均采用 ...
- 英语.Net多语言开发中的问题
问题与现象 多语言开发是一件成本很高的事情. 很多公司会退而求其次选择只开发英文版本的软件分发到不同国家,但这里仍存在不同问题. 我们就遇到了这样的问题,参考下面的代码. CultureInfo cu ...
- 【全】CSS动画大全之按钮【b】
效果预览 代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> < ...
- bx lr
bx lr 的作用等同于 mov pc,lr 即跳转到lr中存放的地址处. 那么lr存放的是什么地址呢? lr就是连接寄存器(Link Register, LR),在ARM体系结构中LR的特殊用途有两 ...
- React 18 自定义 Hook 获取 useState 最新值
原理:通过同步更新 useRef 来获取最新值 // util.ts export const useRefState = (init: any = null) => { const [sta ...
- 华为交换机S5700-52C-EI配置以太网和snmp服务
配置以太网 通过超级终端Hyper Terminal和console串口线链接华为交换机 # 用超级终端打开,配上串口线,用9600波特率链接 system-view interface Vlanif ...