笔者在阅读阿里巴巴Java开发手册时看到这样一段话:
不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用Iterator 方式,如果并发操作,需要对 Iterator 对象加锁正例:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (删除元素的条件) {
iterator.remove();
}
}
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?
笔者疑惑,什么意思,难道不是所有的增强for删除元素都会报错吗?记得是modCount != expectedModCount的时候会报错呀
于是笔者实现了一下,发现…
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
System.out.println(list);
发现居然…
一条小绿条震惊了我
然后我执行第二套代码,也就是把1换成2
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("2".equals(item)) {
list.remove(item);
}
}
System.out.println(list);
呼~报错了,是我满意的结果。
于是,我决定debug一下发生了什么,第一套代码为什么不会报错呢。
首先进到remove方法
很合理,进到fastRemove方法了
modCount++,移动元素,让空出的位置值null让GC清理,很合理,1 被删除了。
然后回来,return true,进行下一个遍历。增强for是会用ArrayList的内部类,来判断是否有下一个元素,cursor是一个游标,判断是否遍历到头了。
可以看到,由于刚才已经删除了“1”,所以size为1,而cursor由于是到索引1了,haxNext的判断游标与长度是相等的,在他看来遍历结束了,返回false。于是遍历结束,这样还没进入到modCount和expectedModCount的比较,遍历就已经退出~
让我们再来看看第二套代码
由于第一个值是“1”,不相等,所以直接进入第二个循环
跟前面一样,会用ArrayList的内部类判断是否遍历结束
这里游标为1,size为2,所以不相等,为true接下来是调用next方法,这个方法依旧是ArrayList的内部类Itr持有
进入checkForComodification方法
可以看到,由于之前没有删除操作,所以这一步是ok的,不会报错。继续走next剩余的方法,没有问题。
接下来因为相等了,执行remove方法
执行fastRemove方法,modCount的值变化
然后增强for循环继续
发现hasNext依旧成立,因为游标cursor为2,而长度为1
于是执行next方法,首先执行checkForComodification
modCount不等于expectedModCount,抛出异常
总结:
原来如此,代码1之所以没有报错,原来是因为它没有进入到modCount和expectedModCount的判断就结束了。但是只要我们明确不能在增强for循环删除、插入元素,还是ok的~。
果然,要了解底层,还是得自己看看源码才知道。