for遍历集合,不能对集合进行remove

问题

有使用过集合的都知道,在用 for 遍历集合的时候是不可以对集合进行 remove操作的,因为 remove 操作会改变集合的大小。从而容易造成结果不准确甚至数组下标越界,更严重者还会抛出ConcurrentModificationException

foreach 遍历等同于 iterator。为了搞清楚异常原因,我们还必须过一遍源码。

源码

public Iterator<E> iterator() {return new Itr();}

原来是直接返回一个 Itr 对象。

从源码可以看出,ArrayList 定义了一个内部类 Itr 实现了 Iterator 接口。在 Itr 内部有三个成员变量。

cursor:代表下一个要访问的元素下标。

lastRet:代表上一个要访问的元素下标。

expectedModCount:代表对 ArrayList 修改次数的期望值,

初始值为 modCount

下面看看 Itr 的三个主要函数。

hasNext 实现比较简单,如果下一个元素的下标等于集合的大小 ,就证明到最后了。

next 方法也不复杂,但很关键。首先判断 expectedModCountmodCount 是否相等。然后对 cursor 进行判断,看是否超过集合大小和数组长度。然后将 cursor 赋值给 lastRet ,并返回下标为 lastRet 的元素。最后将 cursor 自增 1。开始时,cursor = 0,lastRet = -1;每调用一次 next 方法, cursor 和 lastRet 都会自增 1。

remove 方法首先会判断 lastRet 的值是否小于 0,然后在检查 expectedModCountmodCount 是否相等。接下来是关键,直接调用 ArrayList 的 remove 方法删除下标为 lastRet 的元素。然后将 lastRet 赋值给 cursor ,将 lastRet 重新赋值为 -1,并将 modCount 重新赋值给 expectedModCount

原因

ArrayList的remove方法删除元素时,会对modCount的值进行增加。modCount是ArrayList的一个字段,它代表ArrayList被结构性修改的次数。所谓结构性修改,是指那些改变ArrayList大小,或者干扰ArrayList迭代过程的修改。

当调用ArrayList的remove方法删除元素时,ArrayList的内部会先将元素删除,然后modCount的值就会加1,代表ArrayList发生了一次结构性修改。这样,当多线程同时修改ArrayList时,通过检查modCount的值,就可以发现其他线程对ArrayList进行的修改,从而避免产生不可预知的错误。

这个机制在ArrayList的迭代器中得到应用。在迭代过程中,迭代器会检查modCount的值是否和预期的一样,如果不一样,就说明在迭代过程中,有其他线程修改了ArrayList,此时迭代器会抛出ConcurrentModificationException异常,告诉用户ArrayList的结构已经被修改。

expectedModCount是ArrayList的内部类Itr(即ArrayList的迭代器)的一个字段,它的值在迭代器创建时被初始化为ArrayList的modCount值。expectedModCount用于在迭代过程中检查ArrayList是否发生了结构性修改。

当调用ArrayList的remove方法删除元素时,会对modCount的值进行增加,但这并不会改变已经创建的迭代器的expectedModCount的值。expectedModCount的值只在迭代器创建时被初始化,之后就不再改变。

然后在迭代过程中,每次调用迭代器的next或remove方法时,都会检查modCount和expectedModCount的值是否相同,如果不同,那就表示在迭代过程中,ArrayList被其他线程进行了结构性修改,此时会抛出ConcurrentModificationException异常。

如果在迭代过程中,通过迭代器的remove方法删除元素,那么在删除元素的同时,会将expectedModCount的值加1,保持和modCount的值同步,这样就不会抛出ConcurrentModificationException异常。

异常的解决

直接调用 iterator.remove() 即可。因为在该方法中增加了 expectedModCount = modCount 操作。但是这个 remove 方法也有弊端。

1、只能进行remove操作,add、clear 等 Itr 中没有。 2、调用 remove 之前必须先调用 next。因为 remove 开始就对 lastRet 做了校验。而 lastRet 初始化时为 -1。

3、next 之后只可以调用一次 remove。因为 remove 会将 lastRet 重新初始化为 -1

remove。因为 remove 会将 lastRet 重新初始化为 -1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冲鸭的猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值