您的位置:首页 > 编程语言 > Java开发

从Java Collections源码分析迭代器模式

2011-05-31 16:56 726 查看
一、 引言
  
  迭代这个名词对于熟悉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):具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关。
  
  迭代器模式的类图如下:



  

[b]  三、 从Java Collections源码分析迭代器模式[/b]



  下面我们拿出java中Iterator的源码相关源码进行分析。

  下图是Java Collections的整体关系图:



[b] [/b]

  从图中很容易看出,Collection和代表相应的抽象容器角色,而Iterator和ListIterator则代表相应的抽象迭代器角色。

  其中ListIterator是Iterator的子接口,扩充了向后遍历的方法接口。

  以下是Collection,Iterator,List及ListIterator的代码片段:

Collection:

//Collection继承自Iterable接口,而Iterable就是用来生产Iterator的接口
public interface Collection<E> extends Iterable<E> {
...
//继承自Iterable的方法,实现Collection和Iterator的耦合
Iterator<E> iterator();
...
}


Iterator:

public interface Iterator<E> {
boolean hasNext();//如果仍有元素可以迭代,则返回 true。
E next();//返回迭代的下一个元素。
void remove();//从迭代器指向的集合中移除迭代器返回的最后一个元素(可选操作)。
}


List:

//List继承自Collection
public interface List<E> extends Collection<E> {
Iterator<E> iterator();//List和Iterator的耦合
ListIterator<E> listIterator();//List和ListIterator的耦合
ListIterator<E> listIterator(int index);//从列表的指定位置开始
}


ListIterator:

//ListIterator继承自Iterator
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();//以正向遍历列表时,如果列表迭代器有多个元素,则返回 true(换句话说,如果 next 返回一个元素而不是抛出异常,则返回 true)
E next();//返回列表中的下一个元素
boolean hasPrevious();//如果以反向遍历列表,列表迭代器有多个元素,则返回 true
E previous();//返回列表中的前一个元素
int nextIndex();//返回对 next 的后续调用所返回元素的索引
int previousIndex();//返回对 previous 的后续调用所返回元素的索引
void remove();//从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)
void set(E e);//用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)
void add(E e);//将指定的元素插入列表(可选操作)
}


  那么具体容器角色和具体迭代器角色是那些类呢?

  首先先从Iterator入手,让我们先回到图上,熟悉Java Collections框架的朋友很容易明白Collection的实现类很多,它是Java Collections框架的顶层结构,它的下层又分Set和List两个分支,那我们就分别来分析这两个分支具体的具体实现。

  List的第一级实现类为AbstractList,java在此类中实现了具体容器角色和具体迭代器角色,那如何能在一个类中实现两个角色呢,还是从代码入手吧:

//AbstractList实现类List接口
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
//已从结构上修改 此列表的次数。从结构上修改是指更改列表的大小,或者以其他方式打乱列表,使正在进行的迭代产生错误的结果
protected transient int modCount = 0;
...
//生产迭代器,每次生产一个新的对象,所以对同一个容器对象,可以同时进行多个遍历
public Iterator<E> iterator() {
return new Itr();
}
...
//这就是解决一个类实现两个角色的关键,用了一个私有内部类来实现了Iterator
private class Itr implements Iterator<E> {
//游标,也就是被遍历具体容器(AbstractList)的当前索引值
int cursor = 0;
//离游标最近的索引值,主要用在remove方法中,下面会详细说明
int lastRet = -1;
//此变量用作跟踪具体容器(AbstractList)的modCount变量,如果不一致则报错
int expectedModCount = modCount;
//原来hasNext的实现如此简单,用游标和具体容器的size进行比较即可,游标的操作见next方法
public boolean hasNext() {
return cursor != size();
}
//next方法也不难嘛,就是利用游标一次一次对具体容器进行遍历
public E next() {
//检查迭代器运行期间,容器结构是否修改,如修改则报错
checkForComodification();
try {
//呵呵,终于找到你了,原来又是用具体容器的方法实现的,这就是隐藏容器细节的关键
E next = get(cursor);
//记录被返回的索引位置,并使游标向前移动
lastRet = cursor++;
//返回被迭代的当前对象
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
//不用说了,remove方法的实现肯定也是借了容器之力
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
//嘿嘿,又是隐藏细节
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
//因为remove方法改变了容器结构,所以需要同步一下expectedModCount变量使修改次数一致
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
//检查迭代器运行期间,容器结构是否修改,如修改则报错
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
...
//同理,ListIterator的实现类也作为容器的私有内部类出现
private class ListItr extends Itr implements ListIterator<E> {
//具体实现就不介绍了,和Iterator大同小异
ListItr(int index) {
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
public void set(E e) {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
AbstractList.this.add(cursor++, e);
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}


对于再下层的实现类,可以灵活的重写生产方法,并实现专用的迭代器内部类。

  Set和List不同,它的下一层没有实现类,而是俩个接口(AbstractSet和SortedSet),再往下一层找便是我们熟悉的两个实现类HashSet和TreeSet,而了解Java Collection的读者会知道,HashSet和TreeSet其实是借助Map系的HashMap和TreeMap实现的,所以具体实现方法是在Map系中的实现类中实现的(隐藏够深的)。因为涉及的两个对象数据结构稍微有些复杂(散列表和红黑树),可以在此处了解其内部构造。废话少说,上代码:

public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
...
//已从结构上修改 此列表的次数。
transient volatile int modCount;
...
//因散列表结构的特殊性,需要分别实现三个迭代器KeyIterator、ValueIterator和EntryIterator,基类为HashIterator
//基类为HashIterator
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next;	// 散列表的链表的下一个Entry对象
int expectedModCount;	// 与modCount同步的变量
int index;		// 散列表的数组索引
Entry<K,V> current;	// 散列表的链表的当前Entry对象
//构造函数
HashIterator() {
//同步expectedModCount
expectedModCount = modCount;
if (size > 0) {
//取得散列表的数组
Entry[] t = table;
//取得取得散列表的数组中不为空的项目索引并将数组中的链表头赋值给next
while (index < t.length && (next = t[index++]) == null)
;
}
}
//虽然没看到对HashMap的方法调用,但是在构造函数中已经和HashMap对象通信
public final boolean hasNext() {
return next != null;
}
//通过分别遍历散列表中的当前链表和数组,查找下一个Entry对象
final Entry<K,V> nextEntry() {
//检查迭代器运行期间,容器结构是否修改,如修改则报错
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
//取得遍历散列表中的当前链表
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
//如果当前链表遍历到尾,则继续对数组进行遍历
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
//取得最新的链表
current = e;
//返回Entry对象
return e;
}
//使用了HashMap对象的removeEntryForKey进行删除操作,又是隐藏细节。。。
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
//迭代器的具体实现类ValueIterator,继承自HashIterator
private final class ValueIterator extends HashIterator<V> {
//实现next方法,返回Entry对象的Value
public V next() {
return nextEntry().value;
}
}
//迭代器的具体实现类KeyIterator,继承自HashIterator
private final class KeyIterator extends HashIterator<K> {
//实现next方法,返回Entry对象的Key
public K next() {
return nextEntry().getKey();
}
}
//迭代器的具体实现类EntryIterator,继承自HashIterator
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
//实现next方法,返回Entry对象
public Map.Entry<K,V> next() {
return nextEntry();
}
}
//三个工厂方法分别制造三个不同的迭代器
Iterator<K> newKeyIterator()   {
return new KeyIterator();
}
Iterator<V> newValueIterator()   {
return new ValueIterator();
}
Iterator<Map.Entry<K,V>> newEntryIterator()   {
return new EntryIterator();
}

//因上面三个迭代器为HashMap专用,所以不适合Set(ValueIterator和EntryIterator),所以在HashMap的内部类KeySet中又使用了HashMap的KeyIterator,所以HashSet的具体迭代器其实就是HashMap的KeyIterator迭代器
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
private transient Set<Map.Entry<K,V>> entrySet = null;
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
}


  TreeSet的迭代器实现和HashMap很相似,在这里就不再罗嗦了,有兴趣的读者可以去研读一下源码。

对于再下层的实现类,可以灵活的重写生产方法,并实现专用的迭代器内部类。

[b]  三、 总结[/b]



  从代码我们可以得出以下几点:

   1.具体容器角色和具体迭代器角色因为是紧耦合关系,在具体容器中使用内部类将具体迭代器实现是一个很好的做法,方便了具体迭代器直接访问具体容器的所有细节。

   2.内部类为私有,既实现类具体迭代器,又使此容器的专用迭代器对外不可见,迫使使用者进行接口编程。

3.使用迭代器访问一个容器对象的内容而无需暴露它的内部表示。

4.对同一个容器对象,可以同时进行多个遍历。

5.支持以不同的方式遍历一个容器角色。根据实现方式的不同,效果上会有差别。

6.简化了容器的接口。但是在java Collection中为了提高可扩展性,容器还是提供了遍历的接口。

7.为遍历不同的容器结构提供一个统一的接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: