迭代器模式是与集合共生共死的。一般来说,我们只要实现一个容器,就需要同时提供这个容器的迭代器。使用迭代器的好处是:封装容器的内部实现细节,对于不同的集合,可以提供统一的遍历方式,简化客户端的访问和获取容器内数据。在此基础上,我们可以使用 Iterator 完成对集合的遍历,此外,for 循环和 foreach 语法也可以用于遍历集合类。ListIterator 是容器 List 容器族特有的双向迭代器。本文要点主要包括:
迭代器模式是与集合共生共死的。一般来说,我们只要实现一个容器,就需要同时提供这个容器的迭代器,就像 Java 中的 Collection (List、Set 等) ,这些容器都有自己的迭代器。假如我们要实现一个新的容器,当然也需要引入迭代器模式,给我们的容器实现一个迭代器。使用迭代器的好处是:封装容器的内部实现细节,对于不同的集合,可以提供统一的遍历方式,简化客户端的访问和获取容器内数据。
但是,由于容器与迭代器的关系太密切了,所以大多数语言在实现容器的同时也提供了相应的迭代器,并且在绝大多数情况下,这些语言所提供的容器和迭代器都可以满足我们的需要。所以,现实中需要我们自己去实现迭代器模式的场景还是比较少见的,我们常常只需要使用语言中已有的容器和迭代器就可以了。
1、定义与结构
2、举例
由于迭代器模式本身的规定比较松散,所以具体实现也就五花八门,我们在此仅举一例。在举例前,我们先来列举一下迭代器模式的实现方式。
- //迭代器角色,仅仅定义了遍历接口
- public interface Iterator<E> {
- boolean hasNext();
- E next();
- void remove();
- }
- //容器角色,这里以 List 为例,间接实现了 Iterable 接口
- public interface Collection<E> extends Iterable<E> {
- ...
- Iterator iterator();
- ...
- }
- public interface List<E> extends Collection<E> {}
- //具体容器角色,便是实现了 List 接口的 ArrayList 等类。为了突出重点这里指罗列和迭代器相关的内容
- public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
- ……
- //这个便是负责创建具体迭代器角色的工厂方法
- public Iterator iterator() {
- return new Itr();
- }
- //具体迭代器角色,它是以内部类的形式出来的。 AbstractList 是为了将各个具体容器角色的公共部分提取出来而存在的。
- //作为内部类的具体迭代器角色
- private class Itr implements Iterator<E> {
- int cursor = 0;
- int lastRet = -1;
- //集合迭代中的一种"快速失败"机制,这种机制提供迭代过程中集合的安全性. ArrayList 中存在 modCount 对象,增删操作都会使 modCount++ ,通过两者的对比,迭代器可以快速的知道迭代过程中是否存在 list.add() 类似的操作,存在的话快速失败!
- int expectedModCount = modCount;
- public boolean hasNext() {
- return cursor != size();
- }
- public Object next() {
- checkForComodification(); //快速失败机制
- try {
- Object next = get(cursor);
- lastRet = cursor++;
- return next;
- } catch(IndexOutOfBoundsException e) {
- checkForComodification(); //快速失败机制
- throw new NoSuchElementException();
- }
- }
- public void remove() {
- if (lastRet == -1)
- throw new IllegalStateException();
- checkForComodification(); //快速失败机制
- try {
- AbstractList.this.remove(lastRet);
- if (lastRet < cursor)
- cursor--;
- lastRet = -1;
- expectedModCount = modCount; //快速失败机制
- } catch(IndexOutOfBoundsException e) {
- throw new ConcurrentModificationException();
- }
- }
- //快速失败机制
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException(); //抛出异常,迭代终止
- }
- }
3、适用情况
我们可以看出迭代器模式给容器的应用带来以下好处:
1) 支持以不同的方式遍历一个容器角色。根据实现方式的不同,效果上会有差别 (例如,List 中的 iterator 和 listIterator)。
2) 简化了容器的接口。但是在 Java Collection 中为了提高可扩展性,容器还是提供了遍历的接口。
3) 简化了遍历方式。对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于 哈希表 来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。
4) 可以提供多种遍历方式。比如,对于有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
5) 对同一个容器对象,可以同时进行多个遍历。因为遍历状态是保存在每一个迭代器对象中的。
6) 封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
7) 在 Java Collection 中,迭代器提供一种快速失败机制 (ArrayList 是线程不安全的, 在 ArrayList 类创建迭代器之后,除非通过迭代器自身 remove 或 add 对列表结构进行修改,否则在其他线程中以任何形式对列表进行修改,迭代器马上会抛出异常,快速失败),防止多线程下迭代的不安全操作。
由此,也可以得出迭代器模式的适用范围:
1) 访问一个容器对象的内容而无需暴露它的内部表示;
2) 支持对容器对象的多种遍历;
3) 为遍历不同的容器结构提供一个统一的接口(多态迭代)。
1、Iterator 迭代器接口 : java.util 包
Java 提供一个专门的迭代器接口 Iterator,我们可以对某个容器实现该 Interface,来提供标准的 Java 迭代器。
- for(int i=0; i
而 遍历一个 HashSet 又 必须使用 while 循环或 foreach,但不能使用 for 循环:
- while ((e = e.next()) != null) {...e.data()...
- }
对以上两种方法,客户端都必须事先知道集合的类型(内部结构),访问代码和集合本身是紧耦合的,无法将访问逻辑从集合类和客户端代码中分离出来,从而导致每一种集合对应一种遍历方法,客户端代码无法复用。更恐怖的是,如果以后需要把 ArrayList 更换为 LinkedList,则原来的客户端代码必须全部重写。
为解决以上问题,Iterator 模式总是用同一种逻辑来遍历集合:
- for (Iterator it = c.iterater(); it.hasNext();) {...
- }
奥秘在于客户端自身不维护遍历集合的 "指针",所有的内部状态(如当前元素位置,是否有下一个元素)都由 Iterator 来维护,而这个 Iterator 由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。而且,客户端从不直接和集合类打交道,它总是控制 Iterator,向它发送 "向前","向后","取当前元素" 的指令,就可以间接遍历整个集合。
首先看看 java.util.Iterator 接口的定义:
- public interface Iterator {
- boolean hasNext();
- Object next();
- void remove(); // 可选操作
- }
依赖前两个方法就能完成遍历,典型的代码如下:
- for (Iterator it = c.iterator(); it.hasNext();) {
- Object o = it.next(); // 对o的操作... }
多态迭代 : 每一种集合类返回的 Iterator 具体类型可能不同,Array 可能返回 ArrayIterator,Set 可能返回 SetIterator,Tree 可能返回 TreeIterator,但是它们都实现了 Iterator 接口,因此,客户端不关心到底是哪种 Iterator,它只需要获得这个 Iterator 接口即可,这就是面向对象的威力。
2、Iterable 接口 : java.lang 包
Java 中还提供了一个 Iterable 接口,Iterable 接口实现后的功能是 "返回" 一个迭代器。我们常用的实现了该接口的子接口有: Collection
- for (Iterator it = c.iterator(); it.hasNext();) {
- Object o = it.next(); // 对o的操作... }
在 JDK1.5 以及以后的版本中,引进了 foreach,对上面的代码在语法上作了简化 (但是限于只读,如果需要 remove,还是直接使用 Iterator):
- for (Type t: collection) {...
- }
3、思辨
格式如下 :
- for (variable: collection) {
- statement;
- }
定义一个变量用于暂存集合中的每一个元素,并执行相应的语句 (块)。Collection 必须是一个数组或者是一个实现了 lterable 接口的类对象。
可以看出,使用 foreach 循环语句的优势在于更加简洁,更不容易出错,不必关心下标的起始值和终止值。forEach 不是关键字, 关键字还是 for ,语句是由 iterator 实现的,它们最大的不同之处就在于 remove() 方法上。
特别地,一般调用删除和添加方法都是具体集合的方法,例如:
- List list = new ArrayList();
- list.add(...);
- list.remove(...);
- ...
但是,如果在循环的过程中调用集合的 remove() 方法,就会导致循环出错,因为循环过程中 list.size() 的大小变化了,就导致了错误 (
)。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器 iterator 的 remove() 方法,因为它的 remove() 方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的 remove() 方法,调用之前至少有一次 next() 方法的调用。因此,foreach 就是为了让用 iterator 循环访问的形式简单,写起来更方便。当然功能不太全, 所以若是需要使用删除操作,那么还是要用它原来的形式。
1、简述
ListIterator 系列表迭代器,实现了 Iterator
注意,remove() 和 set(Object) 方法不是根据光标位置定义的;它们是根据对调用 next() 或 previous() 所返回的最后一个元素的操作定义的。
2、与 Iterator 区别
Iterator 和 ListIterator 主要区别有:
来源: http://www.bubuko.com/infodetail-1946486.html