迭代

遍历方式

第一种:for 循环。

for (int i = 0; i < list.size(); i++) {
    System.out.print(list.get(i) + ",");
}

第二种:迭代器。

Iterator it = list.iterator();
while (it.hasNext()) {
    System.out.print(it.next() + ",");
}

第三种:for-each。

for (String str : list) {
    System.out.print(str + ",");
}

第一种我们略过,第二种用的是 Iterator,第三种看起来是 for-each,其实背后也是 Iterator,看一下反编译后的代码(如下所示)就明白了。

Iterator var3 = list.iterator();

while(var3.hasNext()) {
    String str = (String)var3.next();
    System.out.print(str + ",");
}

for-each 只不过是个语法糖,让我们开发者在遍历 List 的时候可以写更少的代码,更简洁明了。

Iterator

Iterator 是个接口,用来改进 Enumeration 接口:

Iterable

JDK 1.8 时,Iterable 接口中新增了 forEach 方法。该方法接受一个 Consumer 对象作为参数,用于对集合中的每个元素执行指定的操作。该方法的实现方式是使用 for-each 循环遍历集合中的元素,对于每个元素,调用 Consumer 对象的 accept 方法执行指定的操作。

由于 Iterable 接口是 Java 集合框架中所有集合类型的基本接口,因此该方法可以被所有实现了 Iterable 接口的集合类型使用。它对 Iterable 的每个元素执行给定操作,具体指定的操作需要自己写Consumer接口通过accept方法回调出来。

写得更浅显易懂点,就是:

如果我们仔细观察ArrayList 或者 LinkedList 的“户口本”就会发现,并没有直接找到 Iterator 的影子。反而找到了 Iterable!

事实上,List 的关系图谱中并没有直接使用 Iterator,而是使用 Iterable 做了过渡。

正因此,我们可以使用迭代器遍历List:Iterator it = list.iterator();

为什么要套一层?为什么不直接将 Iterator 中的核心方法 hasNext、next 放到 Iterable 接口中呢?

从英文单词的后缀语法上来看,(Iterable)able 表示这个能不能迭代,而 (Iterator)tor 表示如何迭代能不能怎么做显然不能混在一起,否则就乱的一笔。还是各司其职的好。

原则上,只要一个 List 实现了 Iterable 接口,那么它就可以使用 for-each 这种方式来遍历,那具体该怎么遍历,还是要看它自己是怎么实现 Iterator 接口的。

实现方式

对于ArrayList ,它重写了 Iterable 接口的 iterator 方法。返回的对象 Itr 是个内部类,实现了 Iterator 接口,并且按照自己的方式重写了 hasNext、next、remove 等方法。

Map 就没办法直接使用 for-each,因为 Map 没有实现 Iterable 接口,只有通过 map.entrySet()map.keySet()map.values() 这种返回一个 Collection 的方式才能 使用 for-each。

LinkedList 并没有直接重写 Iterable 接口的 iterator 方法,而是由它的父类 AbstractSequentialList 来完成。

LinkedList 重写了 listIterator 方法:

这里又套了一层迭代器 ListIterator,它继承了 Iterator 接口,在遍历List 时可以从任意下标开始遍历,而且支持双向遍历。ListIterator 就只支持 List

for-each陷阱

for-each 删除元素报错代码:

for-each 本质上是个语法糖,底层是通过迭代器 Iterator 配合 while 循环实现的,来看一下反编译后的字节码。

再挖一层ArrayList 的 iterator 方法:

new Itr() 的时候 expectedModCount 被赋值为 modCount,而 modCount 是 ArrayList 中的一个计数器,用于记录 ArrayList 对象被修改的次数。ArrayList 的修改操作包括添加、删除、设置元素值等。每次对 ArrayList 进行修改操作时,modCount 的值会自增 1。

list 执行了 3 次 add 方法:

  • add 方法调用 ensureCapacityInternal 方法

  • ensureCapacityInternal 方法调用 ensureExplicitCapacity 方法

  • ensureExplicitCapacity 方法中会执行 modCount++

所以 modCount 的值在经过三次 add 后为 3,于是 new Itr() 后 expectedModCount 的值也为 3(回到前面去看一下 Itr 的源码)。

执行 list.remove(str)时:

  • remove 方法调用 fastRemove 方法

  • fastRemove 方法中会执行 modCount++

modCount 的值变成了 4。

第二次遍历时,会执行 Itr 的 next 方法(String str = (String) var3.next();),next 方法就会调用 checkForComodification 方法。该方法对 modCount 和 expectedModCount 进行了比较,发现两者不等,就抛出了 ConcurrentModificationException 异常。这种机制可以保证迭代器在遍历 ArrayList 时,不会遗漏或重复元素,同时也可以在多线程环境下检测到并发修改问题。

结论:不要在 for-each 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式

正确地删除元素

之前执行 list.remove(str)时:

  • remove 方法调用 fastRemove 方法

  • fastRemove 方法中会执行 modCount++

现在执行itr.remove()时,加了一步,删除完会执行 expectedModCount = modCount,保证了 expectedModCount 与 modCount 的同步。

最后更新于