一文搞懂为什么不能在foreach中增加和删除元素

586 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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方法中比较时就会不一致。

image-20221213210600582

image-20221213210656011

image-20221213210720857

为什么使用Iterator进行Remove操作就不会报错呢

原因是ArrayList里面的内部类Itr实现了Iterator接口,里面封装了remove方法,它里面先调用ArrayList自身的remove方法,之后再讲modCount赋值给expectedModCount,所以并不会抛出异常。

image-20221213211226829

那如何正确的删除元素

  1. 使用我们上面推荐的操作,使用Iterator进行删除操作,Iterator的remove方法可以避免fail-fast机制。
  2. 使用remove方法之后直接执行break操作,不进行循环,这样也就不会执行next方法,也就不会执行next方法中的checkForComodification方法。
  3. 使用普通的for循环,这种方式可以达到删除元素的效果,但是会对for循环中后续的元素的遍历造成影响,因为例如删除0位置的元素之后,循环到1,但是此时1下标的值其实是原来下标为2的值,原来下标1的值,已经到当前0下标,但此时循环的变量值是1,也就是原来下标为1的值,没遍历到,如果在删除之后的代码中还会进行获取操作等逻辑,就会导致逻辑错误,不推荐使用。