深入理解集合的迭代器 Iterator,为什么遍历数组时删除元素不能直接使用remove()方法而需要使用迭代器?

发布时间:2026/7/14 13:29:22
深入理解集合的迭代器 Iterator,为什么遍历数组时删除元素不能直接使用remove()方法而需要使用迭代器? 目录一. 了解迭代器 Iterator二. 迭代器使用示范三. 迭代器使用时需要注意的细节3.1 迭代器不会报索引越界异常3.2 迭代器不会自动复位3.3 while 循环中一次只能调用一次 next() 方法3.4 迭代器遍历集合时不能用集合的方法进行增加或删除一. 了解迭代器 Iterator迭代器就是我们 Java 中常说的 Iterator它是一个接口如下图所示在 Iterator 接口中有两个非常重要的方法hasNext()和next()它是迭代器的核心方法。hasNext() 方法的作用是判断当前位置是否有元素返回值为布尔类型next() 方法的作用是获取当前元素并将迭代器移动到下一个位置返回值就是集合中的元素对象迭代器最重要的作用就是遍历集合中的元素Iterator 接口有好几个实现类有的实现类还是其他集合中的内部类这里以 ArrayList 集合为例ArrayList 集合中就有 iterator 这一方法如下图所示该方法中直接返回了一个 Itr 对象我们点击 Itr 跟进查看如下在这里我们就可以看到 Itr 是 Iterator 接口的一个实现类。二. 迭代器使用示范根据下方代码我创建一个数组并添加元素获取迭代器对象public class IteratorTest { public static void main(String[] args) { // 初始化一个数组 ListString list new ArrayListString(16); list.add(0,三); list.add(1,连); list.add(2,外); list.add(3,加); list.add(4,转); list.add(5,发); list.add(6,了); list.add(7,吗); list.add(8,); list.add(9,); // 获取迭代器对象 IteratorString iterator list.iterator(); } }迭代器初始创建的时候如下图它就是默认指向第一个元素的位置下面我们就可以通过迭代器遍历元素了这个时候就会用到上方我说道的两个方法hasNext()和next()我们可以采用 while 循环代码如下public class IteratorTest { public static void main(String[] args) { // 初始化一个数组 ListString list new ArrayListString(); list.add(0,三); list.add(1,连); list.add(2,外); list.add(3,加); list.add(4,转); list.add(5,发); list.add(6,了); list.add(7,吗); list.add(8,); list.add(9,); // 获取迭代器对象 IteratorString iterator list.iterator(); // hasNext() 方法返回值为布尔类型将 iterator.hasNext() 直接作为循环条件 // 只要有值就会一直循环直到遍历到最后一个值 while (iterator.hasNext()){ // next() 方法获取当前元素并将指针移到下一个元素的位置 String str iterator.next(); // 定义字符串变量接受当前元素并打印输出 System.out.println(str); } } }遍历到索引9的位置之后iterator 又向下移动一位此时索引9后面已经没有元素了所以iterator.hasNext() 返回 fasle循环结束我们也可以在控制台得到结果如下所示三. 迭代器使用时需要注意的细节3.1 迭代器不会报索引越界异常如果迭代器已经迭代完成继续调用 next() 方法会爆 NoSuchElementException(没有这个元素异常)不会报索引越界异常因为迭代器 Iterator 本身是不依赖于索引的所以 Iterator 不仅在有序集合 List 中可以使用也可以在无序 Set 中使用。因此它并不会报索引越界异常这一点要记住。如下所示我们在刚才的代码后面继续调用 next() 方法控制台得出的结果如下所示3.2 迭代器不会自动复位迭代器完成迭代之后指针不会复位也可以简单的理解为是一次性的如果我们还想遍历第二遍集合只能重新获取一个迭代器对象再从新开始遍历集合3.3 while 循环中一次只能调用一次 next() 方法这个需要我们注意的是如果我的集合中的元素是偶数个数时调用两次 next() 并不会报错但如果我的集合中的元素是奇数个数时调用两次 next() 就会报错这一点很好解释如下代码我向集合中添加了四个元素一次循环执行两次 next() 方法public static void main(String[] args) { // 初始化一个数组 ListString list new ArrayListString(); list.add(0,aaa); list.add(1,bbb); list.add(2,ccc); list.add(3,ddd); // 获取迭代器对象 IteratorString iterator list.iterator(); // hasNext() 方法返回值为布尔类型将 iterator.hasNext() 直接作为循环条件 // 只要有值就会一直循环直到遍历到最后一个值 while (iterator.hasNext()){ // next() 方法获取当前元素并将指针移到下一个元素的位置 String str iterator.next(); System.out.println(str); String str2 iterator.next(); // 定义字符串变量接受当前元素并打印输出 System.out.println(str2); } }执行如下正常输出当我再添加一个元素 eee 时执行 main 方法就会报 NoSuchElementException(没有这个元素异常)如下图所示很好解释因为我们一次循环执行两次 next()那么指针也会移动两次。在第三次循环的时候第一次 next() 取出的是第五个元素 eee 也就是最后一个元素然后将指针向下移动一位。此时后面已经指向了没有数据的位置那么第二次调用 next() 方法它获取元素时就获取不到了就会爆出 NoSuchElementException(没有这个元素异常)。3.4 迭代器遍历集合时不能用集合的方法进行增加或删除这里我们看源码即可得知Java中 AbstractList 类中维护了 modCount 这一变量默认为0。然后我们看 remove(int index) 根据索引删除元素的方法一旦调用这个方法就会执行 modCount在remove(Object o) 根据对象删除方法中也是一样的调用该方法 modeCountadd()方法也是一样的调用 add() 方法之后内部维护的 modCount 就会加一这里我就不展示了有兴趣的同学看源码就可以知道总而言之言而总之也就是说 modCount 实际上是在记录我们创建的数组修改的次数。重点来啦如下所示Itr 是 ArrayList 的一个内部类它实现了 Iterator 迭代器接口我画线的 expectedModCount 翻译过来意为期望修改次数modCount 赋值给了 expectedModCount也就是说当我们创建了一个数组的 iterator 对象之后当前数组的修改次数 modCount 的值就会赋值给 excepectedModCount。该类中也实现了 Iterator 接口中的 next() 方法如下所示我们的 Iterator 对象每次执行 next() 操作的时候都会执行 checkForComdification() 方法如下所示我们点击跟进该方法查看方法体这里我们看到在该方法中他做了判断expectedModCount 期望修改值 是否与创建 iterator 对象时赋予的值相同如果不同说明创建iterator 之后数组被添加或删除过元素然后就会抛出异常。总结来说就是集合的内部维护了一个 modCount 变量用来记录数组修改过的次数当我们创建了迭代器要去遍历数组时modCount 变量的值就会传递给 Itr 内部迭代器类的其中一个变量当我们在利用迭代器遍历的过程中每次遍历一个元素都会去执行一次 checkForComdification() 方法来判断当前数组的修改次数是否与刚创建迭代器对象时赋予的值相同如果相同说明在迭代期间没有人修改过数组如果不相同则说明我迭代器在迭代数组的过程中有其他人对数组进行过操作就会爆出 ConcurrentModificationException(并发修改异常)。