开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第15天,点击查看活动详情
一文搞懂为什么不能在foreach中增加和删除元素
前言
在阿里巴巴Java规范手册中有一条,不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。今天我们就来聊一聊,为什么不能不能在foreach中增加和删除元素。
fail-fast
首先我们先来了解一种机制,叫fail-fast机制,这种机制是快速失败系统,一般用来停止有缺陷的过程,在遇到异常情况时,直接停止并上报。这是一种错误检测机制。
foreach中增加和删除元素
我们使用ArrayList进行举例,我们在foreach循环遍历list集合时,在循环过程中进行remove操作,此时remove操作会触发一个叫checkForComodification的方法,该方法会比较modCoun和expectedModCount是否一致,不一致则抛出异常。但是我们并没有在代码中看到remove方法调用checkForComodification方法,那为什么在异常的堆栈信息中会有调用呢。原因是foreach是一个语法糖,底层还是通过迭代器来实现,迭代器来实现时,在next方法里面调用了checkForComodification方法。在remove方法中会调用fastRemove,fastRemove中会将modCount++,所以在checkForComodification方法中比较时就会不一致。
为什么使用Iterator进行Remove操作就不会报错呢
原因是ArrayList里面的内部类Itr实现了Iterator接口,里面封装了remove方法,它里面先调用ArrayList自身的remove方法,之后再讲modCount赋值给expectedModCount,所以并不会抛出异常。
那如何正确的删除元素
- 使用我们上面推荐的操作,使用Iterator进行删除操作,Iterator的remove方法可以避免fail-fast机制。
- 使用remove方法之后直接执行break操作,不进行循环,这样也就不会执行next方法,也就不会执行next方法中的checkForComodification方法。
- 使用普通的for循环,这种方式可以达到删除元素的效果,但是会对for循环中后续的元素的遍历造成影响,因为例如删除0位置的元素之后,循环到1,但是此时1下标的值其实是原来下标为2的值,原来下标1的值,已经到当前0下标,但此时循环的变量值是1,也就是原来下标为1的值,没遍历到,如果在删除之后的代码中还会进行获取操作等逻辑,就会导致逻辑错误,不推荐使用。