它用来遍历集合对象。不过,很多编程语言都将迭代器作为一个基础的类库,直接提供出来了。在平时开发中,特别是业务开发,我们直接使用即可,很少会自己去实现一个迭代器。不过,知其然知其所以然,弄懂原理能帮助我们更好的使用这些工具类,所以,还是有必要学习一下这个模式。

List具体的代码实现和使用示例

  1. public interface List<E> {
  2. Iterator iterator();
  3. //...省略其他接口函数...
  4. }
  5. public class ArrayList<E> implements List<E> {
  6. //...
  7. public Iterator iterator() {
  8. return new ArrayIterator(this);
  9. }
  10. //...省略其他代码
  11. }
  12. public class Demo {
  13. public static void main(String[] args) {
  14. List<String> names = new ArrayList<>();
  15. names.add("xzg");
  16. names.add("wang");
  17. names.add("zheng");
  18. Iterator<String> iterator = names.iterator();
  19. while (iterator.hasNext()) {
  20. System.out.println(iterator.currentItem());
  21. iterator.next();
  22. }
  23. }
  24. }

遍历方式对比

遍历集合一般有三种方式:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。相对于 for 循环遍历,利用迭代器来遍历有下面三个优势:

  • 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
  • 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
  • 代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。

    问题:在遍历的同时增删集合元素

    《阿里巴巴 Java 开发手册》的描述如下:

    不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。

通过反编译你会发现 foreach 语法糖底层其实还是依赖 Iterator 。不过, remove/add 操作直接调用的是集合自己的方法,而不是 Iterator 的 remove/add方法
这就导致 Iterator 莫名其妙地发现自己有元素被 remove/add ,然后,它就会抛出一个 ConcurrentModificationException 来提示用户发生了并发修改异常。这就是单线程状态下产生的 fail-fast 机制

fail-fast 机制 :多个线程对 fail-fast 集合进行修改的时候,可能会抛出ConcurrentModificationException。 即使是单线程下也有可能会出现这种情况

如何在遍历的同时安全地删除集合元素?

remove()操作

像 Java 语言,迭代器类中除了的几个最基本的方法之外,还定义了一个 remove() 方法,能够在遍历集合的同时,安全地删除集合中的元素。不过,需要说明的是,它并没有提供添加元素的方法。毕竟迭代器的主要作用是遍历,添加元素放到迭代器里本身就不合适。我个人觉得,Java 迭代器中提供的 remove() 方法还是比较鸡肋的,作用有限。它只能删除游标指向的前一个元素,而且一个 next() 函数之后,只能跟着最多一个 remove() 操作,多次调用 remove() 操作会报错。我还是通过一个例子来解释一下。

  1. public class Demo {
  2. public static void main(String[] args) {
  3. List<String> names = new ArrayList<>();
  4. names.add("a");
  5. names.add("b");
  6. names.add("c");
  7. names.add("d");
  8. Iterator<String> iterator = names.iterator();
  9. iterator.next();
  10. iterator.remove();
  11. iterator.remove(); //报错,抛出IllegalStateException异常
  12. }
  13. }

Collection#removeIf()

Java8 开始,可以使用 Collection#removeIf()方法删除满足特定条件的元素,如

  1. List<Integer> list = new ArrayList<>();
  2. for (int i = 1; i <= 10; ++i) {
  3. list.add(i);
  4. }
  5. list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */
  6. System.out.println(list); /* [1, 3, 5, 7, 9] */

总结

在通过迭代器来遍历集合元素的同时,增加或者删除集合中的元素,有可能会导致某个元素被重复遍历或遍历不到。不过,并不是所有情况下都会遍历出错,有的时候也可以正常遍历,所以,这种行为称为结果不可预期行为或者未决行为。实际上,“不可预期”比直接出错更加可怕,有的时候运行正确,有的时候运行错误,一些隐藏很深、很难 debug 的 bug 就是这么产生的。
有两种比较干脆利索的解决方案,来避免出现这种不可预期的运行结果。一种是遍历的时候不允许增删元素,另一种是增删元素之后让遍历报错。第一种解决方案比较难实现,因为很难确定迭代器使用结束的时间点。第二种解决方案更加合理。Java 语言就是采用的这种解决方案。增删元素之后,我们选择 fail-fast 解决方式,让遍历操作直接抛出运行时异常。
像 Java 语言,迭代器类中除了前面提到的几个最基本的方法之外,还定义了一个 remove() 方法,能够在遍历集合的同时,安全地删除集合中的元素。