一、 引言
迭代这个名词对于熟悉Java的人来说绝对不陌生。我们常常使用JDK提供的迭代接口进行java collection的遍历:
Iterator it = list.iterator();
while(it.hasNext()){
//using “it.next();”do some businesss logic
}
而这就是关于迭代器模式应用很好的例子。
二、 定义与结构
迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。
从定义可见,迭代器模式是为容器而生。很明显,对容器对象的访问必然涉及到遍历算法。你可以一股脑的将遍历方法塞到容器对象中去;或者根本不去提供什么遍历算法,让使用容器的人自己去实现去吧。这两种情况好像都能够解决问题。
然而在前一种情况,容器承受了过多的功能,它不仅要负责自己“容器”内的元素维护(添加、删除等等),而且还要提供遍历自身的接口;而且由于遍历状态保存的问题,不能对同一个容器对象同时进行多个遍历。第二种方式倒是省事,却又将容器的内部细节暴露无遗。
而迭代器模式的出现,很好的解决了上面两种情况的弊端。先来看下迭代器模式的真面目吧。
迭代器模式由以下角色组成:
1) 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。
2) 具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。
3) 容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。
4) 具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关。
迭代器模式的类图如下:
三、 从Java Collections源码分析迭代器模式
下面我们拿出java中Iterator的源码相关源码进行分析。
下图是Java Collections的整体关系图:
从图中很容易看出,Collection和代表相应的抽象容器角色,而Iterator和ListIterator则代表相应的抽象迭代器角色。
其中ListIterator是Iterator的子接口,扩充了向后遍历的方法接口。
以下是Collection,Iterator,List及ListIterator的代码片段:
Collection:
Iterator:
List:
ListIterator:
那么具体容器角色和具体迭代器角色是那些类呢?
首先先从Iterator入手,让我们先回到图上,熟悉Java Collections框架的朋友很容易明白Collection的实现类很多,它是Java Collections框架的顶层结构,它的下层又分Set和List两个分支,那我们就分别来分析这两个分支具体的具体实现。
List的第一级实现类为AbstractList,java在此类中实现了具体容器角色和具体迭代器角色,那如何能在一个类中实现两个角色呢,还是从代码入手吧:
对于再下层的实现类,可以灵活的重写生产方法,并实现专用的迭代器内部类。
Set和List不同,它的下一层没有实现类,而是俩个接口(AbstractSet和SortedSet),再往下一层找便是我们熟悉的两个实现类HashSet和TreeSet,而了解Java Collection的读者会知道,HashSet和TreeSet其实是借助Map系的HashMap和TreeMap实现的,所以具体实现方法是在Map系中的实现类中实现的(隐藏够深的)。因为涉及的两个对象数据结构稍微有些复杂(散列表和红黑树),可以在此处了解其内部构造。废话少说,上代码:
TreeSet的迭代器实现和HashMap很相似,在这里就不再罗嗦了,有兴趣的读者可以去研读一下源码。
对于再下层的实现类,可以灵活的重写生产方法,并实现专用的迭代器内部类。
三、 总结
从代码我们可以得出以下几点:
1.具体容器角色和具体迭代器角色因为是紧耦合关系,在具体容器中使用内部类将具体迭代器实现是一个很好的做法,方便了具体迭代器直接访问具体容器的所有细节。
2.内部类为私有,既实现类具体迭代器,又使此容器的专用迭代器对外不可见,迫使使用者进行接口编程。
3.使用迭代器访问一个容器对象的内容而无需暴露它的内部表示。
4.对同一个容器对象,可以同时进行多个遍历。
5.支持以不同的方式遍历一个容器角色。根据实现方式的不同,效果上会有差别。
6.简化了容器的接口。但是在java Collection中为了提高可扩展性,容器还是提供了遍历的接口。
7.为遍历不同的容器结构提供一个统一的接口。