文章目录

  • 前言
  • foreach
  • 为什么不能使用foreach操作
  • ArrayList迭代器
  • 解析


前言

相信各位程序猿在开发的过程中都用过foreach循环,简单快捷的遍历集合或者数组,但是在通过foreach进行集合操作的时候就不可以了,这是为什么?这里先把问题提出来,接着往下看。

foreach

foreach本质是什么?最初出现在JDK 1.5中,也被称为“增强的for循环”。它的设计目的是提供一种简洁、易读的语法,用于遍历集合或数组中的元素,减少了传统 for循环的冗余代码和错误机会。在使用foreach遍历的前提一定是一个数组或者实现了迭代器的集合才行,所以它的底层是使用迭代器去遍历容器内的数据的。

public class TestForDemo {
    public static void main(String[] args) {
        List<String> list =new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        for (String s : list) {
            System.out.println(s);
        }
    }
}

看上面这段代码是使用foreach进行集合的遍历的,使用的过程也是在这个循环内使用遍历出来的元素,但是如果我们在这个循环内对集合进行操作,比如增加或者删除,可以看一下结果

Java集合为什么不能使用foreach删除元素_for循环


这里判断当元素为D的时候就意味着集合遍历到最后一个元素了,那么我就向集合中添加一个元素E,但是程序报错了,那么正常的方式我们应该使用普通的for循环了对集合进行操作,正确代码是下面这样的。

添加

for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
            if(list.get(i).equals("D")){
                list.add("E");
            }
        }

移除

for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
            if(list.get(i).equals("D")){
                list.remove(i);
                i--;
            }
        }

为什么不能使用foreach操作

这里来带着大家看一下为什么通过foreach来对集合进行添加或者删除这些操作时会保存,首先我们要去看一下集合的内部都有什么,这里以ArrayList为例,其它类型的也是大同小异可以理解一样的。

Java集合为什么不能使用foreach删除元素_java_02


这些是我们通过看ArrayList的内部能直接看到的,下面我们在看看集合的remove方法和add方法

add

Java集合为什么不能使用foreach删除元素_迭代器_03

remove

Java集合为什么不能使用foreach删除元素_windows_04


Java集合为什么不能使用foreach删除元素_集合_05


可以看到在操作集合的时候会去维护一个modCount的成员变量,这个成员变量是在哪呢?它是有ArrayList的父类AbstractList提供的,那么这个变量的意义是干什么的呢,可以看一下注释

Java集合为什么不能使用foreach删除元素_java_06


这段内容意思是

Java 集合类中的 “modCount” 字段的作用。“modCount” 是一个记录集合结构修改次数的字段,用于在迭代器中进行并发修改检测。当集合结构发生变化时,"modCount"值会相应增加。如果在迭代过程中发现 “modCount” 发生意外改变,迭代器会快速失败并抛出ConcurrentModificationException 异常。子类可以通过适当地增加 "modCount"来提供快速失败的迭代器,但每次调用 add 或 remove 方法只能使 "modCount"增加一次。如果不需要提供快速失败的迭代器,可以忽略该字段。总之,“modCount” 确保了迭代器在集合结构修改期间的正确行为。

看完这段大概可以理解为什么我们通过foreach对集合进行添加或者删除这些操作的时候为什么会报错了,但是还是要去迭代器中去确认一下的。

ArrayList迭代器

先找到ArrayList的迭代器,通过获取迭代器的方法去看一看使用的哪个迭代器

Java集合为什么不能使用foreach删除元素_java_07


逐层去查找发现这个迭代器是在ArrayList内部声明的。

Java集合为什么不能使用foreach删除元素_迭代器_08


说到这里就要说一下设计模式中的迭代器模式了,ArrayList提供的迭代器也是实现了Java提供的迭代器接口的,关于迭代器我们知道迭代器是挨个枚举出来的,所以不用关心怎么获取数据。

Java集合为什么不能使用foreach删除元素_java_09


那回到我们的ArrayList的迭代器,它遍历的只能是ArrayList集合,这个迭代器中有记录当前遍历到哪一个元素以及下一个元素的信息的

Java集合为什么不能使用foreach删除元素_集合_10


Java集合为什么不能使用foreach删除元素_windows_11

  1. cursor(光标):cursor 变量表示下一个要返回的元素的索引。在迭代器的 next() 方法中,cursor 会递增,指向下一个元素。它记录了当前迭代器所在位置的索引。
  2. lastRet(上次返回的索引):lastRet 变量表示上一次调用 next() 方法返回的元素的索引。初始值为 -1,表示没有元素被返回过。在迭代器的 remove() 方法中,会使用 lastRet 来删除上一次返回的元素。
  3. expectedModCount是一个变量,用于在迭代器(Iterator)中跟踪集合类的修改次数。在迭代器初始化时,expectedModCount 会被赋值为集合类的 modCount(修改次数)。modCount 是一个计数器,记录了集合被修改的次数。每当对集合进行增加、删除等操作时,modCount 会递增。

解析

为什么foreach对集合进行添加或者删除会报错,是因为foreach底层使用的迭代器,而我们在这个循环内使用的移除或者添加操作是使用的集合的这两个是有区别的,可以看到迭代器使用集合的remove方法移除元素然后将记录集合修改次数的属性同步给自己的expectedModCount属性来确保一直,但是我们通过foreach进行遍历,移除元素时却使用集合的remove方法那么在遍历过程中就会出现上面那个异常。而这个异常是怎么被抛出呢?是迭代器每次去获取数据以及移除数据时都会调用一个checkForComodification方法,这个方法内部就去比对集合的modCount和迭代器的expectedModCount是否一直,如果一直则正常执行,如果不一致则抛出ConcurrentModificationException

Java集合为什么不能使用foreach删除元素_for循环_12


Java集合为什么不能使用foreach删除元素_集合_13


Java集合为什么不能使用foreach删除元素_for循环_14