设计模式——行为型:迭代器模式

一、迭代器模式定义

迭代器模式(Iterator Design Pattern),用来遍历集合对象,“集合对象” 也可以叫作“容器”、“聚合对象”,实际上就是包含一组对象的对象,比如:数组、链表、树、跳表。

迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更单一。

迭代器是用来遍历容器的,所以,一个完整的迭代器模式,一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器借口、迭代器实现类。容器对象通过依赖注入传递到迭代器类中。

遍历集合一般有三张方法:for循环、foreach循环、iterator迭代器。

List<Integer> list = new ArrayList<>();

# for循环
for(int i = 0; i < list.size(); i ++) {
          
   
	list.get(i);
}

# foreach循环
for(Integer a : list) {
          
   

}

# iterator迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
          
   
    Object object = iterator.next();
    // do something
}

后两种本质是属于一种,都可以看作迭代器遍历,相对于for循环利用迭代器来遍历的优势:

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

二、为什么不能在遍历集合的同时,进行增删集合元素?

在通过迭代器来遍历集合元素的同时,增加或者删除集合中的元素,有可能会导致某个元素被重复遍历或者遍历不到,不过并不是所有情况下都会遍历出错,有时也可以正常遍历,这种行为称为结果不可预期行为或者未决行为,即结果是否正确视情况而定。 如:ArrayList底层是对应数组,为了保持数组存储数据的连续性,数组的删除操作会涉及元素的搬移,会导致游标指向改动,如果删除的正是游标所指或前面的元素,则会出现元素遍历不存在的结果。

如何应对遍历时改变集合导致的未决行为?

一种是遍历的时候不允许增删元素,另一种是增删元素之后让遍历报错。

第一种解决方案比较难实现,因为很难确定迭代器使用结束的时间点。Java中采用的是第二种,增删元素之后,选择fail-fast 解决方式,让遍历操作直接抛出运行时异常。

如ArrayList底层中定义了一个成员变量modCount,记录集合被修改的次数,集合每调用一次增加或删除元素的函数,modCount加1.当通过调用集合上的 iterator() 函数来创建迭代器时,会把modCount 值传递给迭代器的expectedModcount成员变量,之后每次调用集合上的hasNext()、next()、currentItem() 函数都会检查集合上modCount 和 expectedModcount 是否相等,两个值不相等,说明进行了增删,即抛出运行时异常结束程序。

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

Java迭代器中提供了remove()方法,能在遍历集合的同时,安全的删除集合中的元素,但其只能删除游标指向的前一个元素,而且一个next()函数后只能跟着最多一个remove()操作,多次调用remove()操作会报错

Iterator iterator = list.iterator();
iterator.next();
iterator.remove();
iterator.remove(); // 操作会报错

Java实现中,迭代器是容器类的内部类,并且next() 函数不仅将游标后移一位,还会返回当前的元素,迭代器类新增了一个lastRet成员变量,用来记录游标指向的前一个元素,通过迭代器来删除这个元素时,可以更新迭代器中的游标和lastRet值,来保证不会因为删除元素而导致某个元素变量不到。

经验分享 程序员 微信小程序 职场和发展