在foreach循环里进行元素的remove/add操作会产生的问题

阿里Java开发手册中有这么一条对foreach中进行remove/add操作的约束

image-20210814145544368

在反例中,如果判断条件为"1"不会报错,但是如果判断条件变成"2"则会抛出java.util.ConcurrentModificationException异常

下面我们先来分析下为什么"2"会报错

通过报错的堆栈信息可以看出是从at java.util.ArrayList$Itr.next(ArrayList.java:859)方法抛出的异常,但是我们并没有调用next方法啊?

通过查看字节码可以看到foreach其实就是通过while+iterator来实现的

List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator var2 = list.iterator();

while(var2.hasNext()) {
  String s = (String)var2.next();
  if (s.equals("2")) {
    list.remove(s);
  }
}

先看一下iterator是如何判断还有下一个元素的

public boolean hasNext() {
  return cursor != size;
}
// cursor 游标,初始值为0,每一次调用next()自增1
// size 集合大小

点到源码中看一下,next函数的第一行就是一个校验

final void checkForComodification() {
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}
// modCount是记录集合改变的次数,调用add、remove方法都会使其递增
// 而expectedModCount是用于记录modCount的初始值
hasNextmodCountexpectedModCount
初始值22
第一次while,不满足equals0 != 2 true22
第二次循环,满足equals1 != 2 true32
第三次循环2 != 1 true--

问题就处在第二次循环删除了一个元素后,modCount已经不等于expectedModCount了,但是第三次循环因为元素被删除导致hasNext为true,继续执行next走进checkForComodification校验抛出的异常

所以在迭代器中调用list自身的remove或者add方法都会抛异常,建议使用迭代器的remove方法

至于案例一不报错,就是属于一种巧合的情况了

hasNextmodCountexpectedModCount
初始值22
第一次while,满足equals0 != 2 true32
第二次循环,不满足equals1 != 1 false--

由于第一次循环就删除了一个元素,在下次循环hasNext的判断时 cursor 刚好等于 size就返回false直接结束循环了,从而没机会调用next抛异常,同理如果刚好删除的是集合倒二位的元素都不会报错

至于hasNext为什么不直接使用cursor < size来判断,我认为可能是ConcurrentModificationException本身设计就是为了使某些集合在进行迭代时检测到修改而快速失败的,该设计本身就不允许或不建议开发者进行这种操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值