foreach里不能list.remove(i)的原因

文章讲述了在Java中使用foreach遍历List时删除元素可能导致ConcurrentModificationException的原因,涉及迭代器的工作原理和ArrayList的fail-fast机制。作者指出,正确的方法是使用迭代器的remove方法,以保持预期修改次数的一致性。
摘要由CSDN通过智能技术生成

image-20240425204318119

image-20240425204330341

通过上述代码,我们可以发现是可以删除成功的

先看foreach反编译后的具体原理代码

image-20240425204405821

可以发现是使用迭代器进行遍历的(表层就是将list集合,你也可以把他看成3颗子弹,装进了手枪的弹夹里,需要遍历的时候就开枪射出)

使用while循环,如果有下一个hasNext为true,则获取下一个的值,var2.next(),同时进行判断,如果是1号,则remove删除,这好像看起来没什么问题,我们再次跟进list也就是ArrayList的源码查看

image-20240425202425264

只要调用了iterator生成迭代器方法就会返回ArrayList类里的Itr实例,并在遍历中使用hasNext,next方法

image-20240425203001577

hasNext:判断当前游标指向的位置cursor,未显示定义值则默认为0,而size是列表的长度为3,因此0!=3,为真则执行var2.next()方法

image-20240425204047483

进入next方法后首先执行checkForComodification,我们继续跟进

image-20240425204132523

这是一个常量无返回值方法,判断modCount!=expectedModCount

  • modCount,列表实际修改次数,modified:修改

  • expectedModCount,期望修改次数(迭代器脑海里认为他已经对该列表修改的次数)

  • image-20240425210202335

    我们只要一调用iterator迭代器,则会实例Itr,并且初始值expectedModCount

     int expectedModCount = modCount;

我们第一次调用获取到"1号"并删除时,是正常的,但是在remove的时候,他调用的是ArrayList自身的remove方法,体现为 list.remove(member)

image-20240425220801498

可以发现此时modCount++了(add,clear方法也会时modCount自增),第一次迭代遍历结束,进行第二次

image-20240425212238311

hasNext判断,cursor!=size,为真,执行next

image-20240425212256063

执行check判断 真实修改值和迭代器认为修改值做if判断

  if (modCount != expectedModCount)
                 throw new ConcurrentModificationException();

image-20240425212420977

问题就出现在这一步,由于expectedModCount是我们一开始调用iterator,并获取实例Itr时由modCount赋予的初始值,而我们的modCount此时已经由list.remove这个操作引起了改变,也就是真实修改次数变化了,此时if判断为真,抛出异常,ConcurrentModificationException:并发修改异常

那么为什么要判断这两个值是否相同呢?从异常名字可以看出,应该是为了防止并发操作/多线程操作对列表进行修改

这其中原因是触发了fail-fast快速失败机制,也就是modCount变量的由来

image-20240425215128119

当字段的值发生意外变化,说明集合已经被修改,数据不一致,此时为了避免这种情况,抛出异常

通俗点理解就是 你有一个游戏账号,每天都得进行签到操作,第一天,你进行了签到,实际签到天数为1(modCount),而你脑子里认为的签到过的次数(expectedModCount)也为1,这并没有什么问题,然而,第二当你想要进行签到的时候,你发现你的实际签到天数居然变成了两天,这与你印象中的签到次数不一致(modCount != expectedModCount),此时你会发现你的账号是否被人盗取,此时你会询问客服具体原因( throw new ConcurrentModificationException();)

那该怎样删除?

  1. 使用迭代器自己的方法进行删除,原因是他会在删除后更新expectedModCount,从而绕过checkForComodification()里的判断

image-20240425220915012

 //给列表装配一个迭代器
         Iterator<String> iterator = list.iterator();
         while (iterator.hasNext()){
             //获取遍历到的元素
             String next = iterator.next();
             if ("1号".equals(next)){
                 //删除当前元素
                 iterator.remove();
             }
         }
         //输出查看结果
         System.out.println(list);

image-20240425221203168

  1. 第二种写法

  for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
             if ("1号".equals(iterator.next())) {
             iterator.remove();
             }
         }
         System.out.println(list);
  1. 第三种,普通for循环的删除

image-20240425224812939

如果不使用i--则会产生如下效果

这里自减的原因是因为删除掉一个元素之后后面的元素会前移一个坐标,与i++抵消,不遍历下一个,否则会产生漏删的情况

image-20240425224540483

或者是从后往前,这样就不会出现后面的元素向前位移的问题了

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值