class="markdown_views prism-atom-one-light">
定义
迭代器(
Iterator
)模式,从其英文单词可以看出,有反复做某件事的意思。迭代器模式常用于数据集合中,对数据集合中的数据按照顺序进行遍历。它能提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。
问题引入
假设我们有一个书架,假设书架可以按照顺序放置无数本书,现在有一个需求,那就是遍历书架里面的所有的书籍,将书籍的名称打印出来。
常规的解决办法
一般情况下,我们在用代码来解决该问题的时候,都是想到使用
for
循环,对书架中的每一本书进行循环遍历,然后输出名称。这是一种比较常规的做法,考虑的问题就是为了遍历书架中的书籍,并使用变量
i
来记录当前遍历到哪一本书了,以及将指针指向下一本书,伪代码如下:
List books = new ArrayList<>();
books.add(new Book("深入理解Java虚拟机"));
books.add(new Book("深入分析Java Web技术内幕"));
books.add(new Book("Java编程思想"));
books.add(new Book("Linux就该这么学"));
for (int i = 0; i < books.size(); i++) {
System.out.println(books.get(i).getName());
}
这里的
for
语句中的
i++
的作用是将
i
的值在每一次循环之后就自增
1
,使得在遍历的过程中就可以访问集合中的下一本书籍,下下一本书籍,再下下一本书籍,直到遍历完所有的书籍。如果我们考虑将
i
的作用进行抽象化,使其通用化,那么就可以形成迭代器模式。
从List中获取启发
说到迭代器设计模式,大家肯定会想到
java.util
包中的集合,是的,
Java
中集合类中运用到了迭代器设计模式,那么,我们在学习迭代器设计模式的时候,完全可以通过去学习集合的源码来学习迭代器设计模式,下面,我将从
ArrayList
出发,探究一下在
ArrayList
中如何运用的迭代器设计模式。
作为集合类的顶级接口,
Collection
接口继承了
Iterable
接口,
Iterable
接口的子接口或者实现类都具备迭代和遍历的功能,那么List接口继承自
Collection
,自然也是具备基本的迭代功能的,那么我们从
List
出发,来探究迭代器模式的运用。
List
接口中有一个重写自
Collection
的
iterator()
方法,它的返回值是一个
Iterator
接口的实现类对象。
那么我们接着看
ArrayList
类,它实现了
List
接口,也实现了
List
接口中的
iterator()
方法。
那么,针对实例化
ArrayList
的时候传入的具体的泛型,可以生成其对应的迭代器,也就是说,上图中
new
语句后面的
Itr()
创造出来的迭代器对象肯定是针对传入的泛型的迭代器对象。我们继续阅读代码,发现
Itr
是
ArrayList
的私有内部类,它还实现了
Iterator
接口,也实现了基本的
hasNext()
方法和
next()
方法。
因为
Itr
是
ArrayList
的内部类,那么
Itr
可以操作
ArrayList
的成员变量,而
ArrayList
作为集合,它内部的元素是存储在变量名为
elementData
的
Object
数组中的,整个遍历的过程就是针对这个数组进行遍历的。
分析完源码,我们也许还是有些迷糊,那么我们需要借助UML类图来描述这些接口或者类之间的关系了。
从上图可以看出,
List
接口中有创建
Iterator
接口的实现类对象的抽象方法,而
ArrayList
实现了
List
接口,而
Itr
类实现了
Iterator
接口,且其拥有
ArrayList
的元素数组,可以针对该数组进行一系列的操作,比如遍历。类图可以很清晰表表现出类与类之间的关系,不太熟悉的可以去学习一下
UML
类图。
手动实现迭代器设计模式
从
List
中获取到迭代器设计模式实现的灵感,那么我们需要自己手动实现自己的迭代器模式,来解决文章开始引入的遍历书架的问题。首先,我们根据
List
中采用的迭代器设计模式,我们需要一个集合接口
Aggregate
(有“使聚集”、“聚合”的意思),该集合接口中有创建迭代器接口
Iterator
实现类对象的抽象方法
iterator
,这个迭代器接口有
hasNext
、
next
方法,用来判断集合是否还有元素以及获取集合元素,我们还需要具体的集合实现类,也就是具体的书架,用来承载书籍,该实现类实现了
Aggregate
集合的
iterator
方法,最后,我们还需要实现迭代器接口的具体类,它持有集合的具体的实现类,还实现了
hasNext
和
next
方法。我们将类图设计如下所示:
根据类图,我们可以很轻松地设计出基本的迭代器设计模式及的代码,主要代码如下所示:
package cn.itlemon.design.pattern.chapter01.iterator.example;
/**
* 是需要遍历的集合的接口
*
* @author jiangpingping
* @date 2018/8/27 下午9:31
*/
public interface Aggregate<E> {
/**
* 返回当前集合的迭代器
*
* @return 当前集合的迭代器
*/
Iterator iterator();
}
该接口遵循类图的设计,接口中包含创建
Iterator
接口实现类对象的方法。
package cn.itlemon.design.pattern.chapter01.iterator.example;
/**
* 迭代器接口
*
* @author jiangpingping
* @date 2018/8/27 下午9:32
*/
public interface Iterator<E> {
/**
* 检测是否有下一个元素
*
* @return 如果有,返回true,否则返回false
*/
boolean hasNext();
/**
* 返回当前元素,并将指针指向下一个元素
*
* @return 当前元素
*/
E next();
}
该迭代器接口有两个方法,分别是判断被遍历的集合是否还有元素以及获取集合中元素并将指针指向下一个元素。
package cn.itlemon.design.pattern.chapter01.iterator.example;
import java.util.ArrayList;
/**
* 书架,其实就是承载书籍的集合或数组
*
* @author jiangpingping
* @date 2018/8/27 下午9:42
*/
public class BookShelf<E> implements Aggregate {
private ArrayList books;
private int last = 0;
public BookShelf(int initSize) {
books = new ArrayList<>(initSize);
}
public E getBookAt(int index) {
return books.get(index);
}
public void appendBook(E e) {
books.add(e);
last++;
}
public int getLength() {
return last;
}
@Override
public Iterator iterator() {
return new BookShelfIterator<>(this);
}
}
getBookAt
方法可以获取指定索引位置的书籍,
appendBook
方法是向书架中最后一个位置添加书籍的方法,
getLength
方法可以得到书籍的数量,
iterator
方法可以获取当前书架的迭代器实现类对象。
package cn.itlemon.design.pattern.chapter01.iterator.example;
/**
* {@link BookShelf} 的迭代器
*
* @author jiangpingping
* @date 2018/8/27 下午9:49
*/
public class BookShelfIterator<E> implements Iterator<E> {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
index = 0;
}
@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}
@Override
public E next() {
E e = bookShelf.getBookAt(index);
index++;
return e;
}
}
hasNext
方法可以判断遍历的集合中是否还有未遍历的元素,
next
方法直接获取元素,并将在指针指向下一个元素,所以在使用
List
的迭代器的时候,要特别留意
next
方法,在每次循环的过程中,
next
方法只能使用一次,多次使用的话,单次遍历会引起逻辑错误。
package cn.itlemon.design.pattern.chapter01.iterator.example;
/**
* 书籍的实体类
*
* @author jiangpingping
* @date 2018/8/27 下午9:39
*/
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package cn.itlemon.design.pattern.chapter01.iterator.example;
/**
* 测试Iterator模式的主类
*
* @author jiangpingping
* @date 2018/8/27 下午10:01
*/
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf<>(4);
bookShelf.appendBook(new Book("深入理解Java虚拟机"));
bookShelf.appendBook(new Book("深入分析Java Web技术内幕"));
bookShelf.appendBook(new Book("Java编程思想"));
bookShelf.appendBook(new Book("Linux就该这么学"));
Iterator iterator = bookShelf.iterator();
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.println(book.getName());
}
}
}
看到上面的测试主类,和使用
ArrayList
的迭代器一模一样,也就是说,我们手动实现了迭代器设计模式,其实,在设计过程中,所有的泛型都是使用
E
来进行代替的,所以这些类不仅仅适用
Book
的遍历,支持其他类的遍历,如果在将类的名称修改一下,改成更加通用的,那么这个迭代器就将适合更多的集合遍历,遍历的元素可以是其他的类对象。
浅析迭代器模式中的重要角 {MOD}
任何模式都是前人积累下来的经验,大多数模式中都有固定的角 {MOD},使用模式的时候,可以根据固定的角 {MOD}来编写代码,就可以轻松地利用上设计模式,使自己的代码更具有“可复用性”。接下来将浅析迭代器设计模式中的重要角 {MOD}。
- Iterator(迭代器接口)
该角 {MOD}负责定义按顺序逐个遍历元素的接口(API
),在本次示例中,由Iterator
接口扮演了这个角 {MOD},它定义了两个方法,hasNext
和next
方法,分别是判断被遍历的集合或者数组是否还有下一个元素以及直接获取元素,并将指针指向下一个元素。
- ConcreteIterator(具体的迭代器)
该角 {MOD}实现了Iterator
所定义的接口(API
),在本次示例中,由BookShelfIterator
类来扮演了这个角 {MOD},它持有需要遍历的数组或者集合的具体信息,也就是说需要被遍历的集合存在于这个迭代器中。
- Aggregate(集合)
该角 {MOD}负责定义创建Iterator
角 {MOD}的接口(API
),这个接口会创建出一个Iterator
的实现类对象,这个实现类对象可以对实现Aggregate
集合接口类对象进行遍历。本次示例中,由Aggregate
接口来扮演这个角 {MOD}。
- ConcreteAggregate(具体的集合)
该角 {MOD}负责实现Aggregate所定义的接口(API
),它会创建出具体的Iterator
角 {MOD},也就是ConcreteIterator
角 {MOD}。在本次示例中,由BookShelf
扮演了这个角 {MOD},它实现类iterator
方法。
迭代器设计模式UML类图
为什么要使用迭代器设计模式
为什么在遍历的时候要额外引入迭代器这种复杂的模式呢?
也许你有这样的疑问,引入迭代器设计模式的一个重要理由是:将实现和遍历进行了分离,也就是说遍历的过程完全不依赖与你所选择的集合是如何实现的,在示例中,使用的
ArrayList
来承载数据的,如果开发者换了其他的容器来承载数据,那么只需要修改集合的实现方式即可,完全不需要去修改遍历的过程代码,这就提高了代码的“可复用性”和“可靠性”。