迭代器模式
在容器里存放了大量的同类型对象,我们如果想逐个访问容器中的对象,就必须要知道容器的结构。比如,ArrayList 和 LinkedList 的遍历方法一定是不一样的,如果再加上HashMap, TreeMap,以及我们现在正在研究的BinarySearchTree,对于容器的使用者而言,肯定是一个巨大的负担。作为容器的使用者,我肯定希望所有的容器能提供一个统一的接口,这个接口可以遍历容器中的所有内容,又可以把容器的细节屏蔽掉。
Iterator上定义了各种方法,用于遍历。
例如:
public static void main(String args[]) {
LinkedList<Integer> ll = new LinkedList<>();
for (int i = 0; i < 10; i++)
ll.add(i);
Iterator<Integer> ii = ll.iterator();
while (ii.hasNext())
System.out.printf("%d\t",ii.next());
System.out.println();
ArrayList<Integer> al = new ArrayList<>();
for (int i = 0; i < 10; i++)
ll.add(i);
Iterator<Integer> aii = al.iterator();
while (aii.hasNext())
System.out.printf("%d\t",aii.next());
}
可以看到,我们根本就不需要关心后面具体的数据结构,只需要使用 Iterator,不管后面是数组还是链表,都可以很方便地遍历整个容器。
接口定义:
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Iterator<E> {
boolean hasNext();
E next(); // 取出下一个元素
void remove();
}
用Iterator模式实现遍历集合
Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
例如,如果没有使用Iterator,遍历一个数组的方法是使用索引:、
for(int i=0; i<array.size(); i++) { ... get(i) ... }
而访问一个链表(LinkedList)又必须使用while循环:
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<E> {
boolean hasNext();
E 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接口即可,这就是面向对象的威力。
所有集合类都实现了 Collection 接口,而 Collection 继承了 Iterable 接口。
public interface Collection<E> extends Iterable<E> { }
而在具体的实现类中(比如ArrayList),则在内部维护了一个Ite内部类,该类
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
在遍历元素或者删除元素时,必须要进行next()方法,要不然,迭代器不能正常运行:如
/**
*太极云软件技术股份有限公司版权所有 1990-2016. http://www.tyky.com.cn
* @file com.hoojjack.spring_study.tutorialspoint
*
*/
package com.hoojjack.spring_study.tutorialspoint;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @ClassName: TestIterator
* @Description: TODO(这里用一句话描述这个类的作用)
* @author hoojjack
* @date 2017年5月15日 下午6:42:15
*
*/
public class TestIterator {
public static void main(String args[]){
List<String> list = new ArrayList<String>();
list.add("h");
list.add("o");
list.add("o");
list.add("j");
Iterator<String> it = list.iterator();
while(it.hasNext()){
//it.next();//注释这行会报错,具体原因请看源码
it.remove();
break;
}
for(String i:list){
System.out.println(i);
}
}
}
为什么一定要去实现Iterable这个接口呢?为什么不直接实现Iterator接口呢?
看一下JDK中的集合类,比如List一族或者Set一族,都是实现了Iterable接口,但并不直接实现Iterator接口。 仔细想一下这么做是有道理的。
因为iIterator接口的核心方法next()或者hasNext()是依赖于迭代器的当前迭代位置的。如果Collection直接实现Iterator接口,势必导致集合队中中包含当前迭代位置的数据(指针)。当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知。除非再为Iterator接口添加一个reset()方法,用来重置当前迭代位置。 但即时这样,Collection也只能同时存在一个当前迭代位置。 而Iterable则不然,每次调用都会返回一个从头开始计数的迭代器。 多个迭代器是互不干扰的。
直接继承Iterator,从Iterator接口可以看出需要子类实现next(),hasNext()方法,假设都交给List去实现,那么获取list的Iterator()时就会出现上面解释那样,而继承Iterable接口,则需要每次List返回一个新的Iterator对象(见ArrayList的new Itr()),因此可以避免上述说到的原因。