迭代器模式(Iterator Pattern
),属于行为型设计模式,在java
和python
中十分常见。目的是在不暴露集合内部结构的条件下,顺序访问该集合内部的元素。
提供一种顺序访问一个集合中所有元素的方式, 而又无需暴露该对象的内部表示。
在需要顺序访问集合元素的场景中,传统方式是通过以下方式对集合元素进行遍历
for(int i=0; i<list.size(); i++) {
Object obj = list.get(i);
}
在该方式中,对元素的获取是通过直接调用集合的get()
方法完成的,即对元素的遍历由集合本身负责。
而使用迭代器设计模式后,集合本身不负责元素的遍历,而提供一个获取迭代器的方法iterator()
,对元素的遍历由迭代器负责,如下所示
Iterator<Object> iterator = list.iterator();
while(iterator.hasNext()) {
Object obj = iterator.next();
}
这是一种对责任细分的体现,集合将对元素遍历的责任交给迭代器完成。
迭代器的使用也十分简单,一般来讲,只提供两个方法:hasNext()
和next()
。hasNext()
方法用于遍历集合,next()
方法用于顺序获取集合中的元素。
在迭代器模式中,所有类型的集合(无论底层是数组还是链表),都需要提供一个获取迭代器的方法,因此我们可以将该方法抽象出来封装到一个独立的**抽象接口Iterable
**中,实现该接口的任何集合都具备获取迭代器的能力。
迭代器无论采取什么样的遍历方式,都需要两个基本方法hasNext()
和next()
,因此我们将这两个方法抽象到接口类Iterator
中。
因此进过分析,可以确定在迭代器模式中,存在四个基本角色:支持迭代的接口类、具体集合类、迭代器抽象接口类、具体迭代器类。
支持迭代的集合抽象接口(BarIterable
)
该接口定义一个获取迭代器的方法iterator()
,实现该接口的所有集合类都需要实现对应的逻辑。
另外,在声明该抽象接口时,还需要在接口上声明一个泛型
,因为这是一个集合接口,意味着集合中的元素可以是任意类型。同理,iterator()
方法返回的迭代器对象也应该标注泛型
。
具体的集合类(BarList
)
实现抽象接口(BarIterable
),按照本身的实际情况对iterator()
方法进行实现。
与上面接口类BarIterable
类似,该集合类和其实现的iterator()
方法也应该各声明一个泛型
。
迭代器抽象接口(FooIterator
)
该接口定义了迭代器的基本行为,遍历和获取,分别用hasNext()
方法和next()
方法表示。
作为迭代器,也应该在类上声明一个泛型
,原因同上。因此其next()
方法的返回值也应该是泛型
。
迭代器具体实现类(FooItr
)
实现迭代器抽象接口FooIterator
。
由于迭代器的功能是对集合中的元素进行遍历,因此我们常用的做法是将具体迭代器声明为集合类的内部类,这样迭代器就可以直接访问集合中的元素了。
因此迭代器模式的通用UML图如下所示
根据以上对迭代器模式中各个角色的分析,我们使用代码对其进行演示
BarIterable
)新建接口类BarIterable
,并定义iterator()
方法,同时声明接口类的泛型
。
public interface BarIterable<T> {
/** 获取迭代器 **/
FooIterator<T> iterator();
}
BarList
)新建集合类BarList
,实现接口BarIterable
。
public class BarList<T> implements BarIterable<T> {
private final Object[] array = new Object[]{1,2,3,4,5,6,7,8,9};
@Override
public FooIterator<T> iterator() {
return new FooItr();
}
}
FooIterator
)新建迭代器接口类FooIterator
,并定义hasNext()
方法和next()
方法,同时声明泛型
。
public interface FooIterator<T> {
boolean hasNext();
T next();
}
FooItr
)新建迭代器实现类FooItr
,实现接口类FooIterator
。
由于迭代器的功能是对集合中的元素进行遍历,因此我们常用的做法是将具体迭代器声明为集合类的内部类,这样迭代器就可以直接访问集合中的元素了。
下面我们在集合类BarList
中定义该内部类
public class BarList<T> implements BarIterable<T> {
private final Object[] array = new Object[]{1,2,3,4,5,6,7,8,9};
@Override
public FooIterator<T> iterator() {
return new FooItr();
}
private class FooItr implements FooIterator<T> {
private Integer index = 0;
@Override
public boolean hasNext() {
return index < array.length;
}
@Override
public T next() {
return (T) array[index++];
}
}
}
在该内部类中,我们通过一个属性index
index
属性
用来访问集合中的数组结构,作为数组下标从数组中读取数据。
hasNext()
方法
数组中长度是固定的,根据当前index
判断是否已经到达最后一个元素
next()
方法
直接通过index
作为数组下标,从数组中读取数据。
新建一个测试类IteratorDemo
,在main()
方法中测试代码
public class IteratorDemo {
public static void main(String[] args) {
BarList<Integer> barList = new BarList<>();
FooIterator<Integer> iterator = barList.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
System.out.print(integer + " ");
}
}
}
运行后输出如下所示
在java的集合框架中,我们都知道所有的集合类都实现了Collection
接口,但Collection
接口并不是集合的顶级接口,Iterable
接口才是,也就是说,所有的集合类都实现了迭代器模式。下面是java集合框架的类图
我们以ArrayList
和LinkedList
为例,按照上面我们对迭代器模式各个角色的分析,来看一下java集合是如何应用迭代器模式的
Iterable
接口Iterable
接口如下所示,它定义了一个获取迭代器的方法iterator()
public interface Iterable<T> {
/** 获取迭代器 */
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
Iterator
public interface Iterator<E> {
/** 是否到达集合中最后一个元素 */
boolean hasNext();
/** 获取集合中下一个元素 */
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
ArrayList
集合对迭代器模式的实现public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
transient Object[] elementData;
// 省略无关代码
// 获取迭代器
public Iterator<E> iterator() {
return new 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;
Itr() {}
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();
// 数组下标+1
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
// ...
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
// ...
}
final void checkForComodification() {
// ...
}
}
}
LinkedList
集合对迭代器模式的实现public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient Node<E> first;
transient Node<E> last;
// 获取迭代器
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
// ListIterator接口继承于Iterator接口,对Iterator接口定义的方法进行补充
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
// 省略无关代码
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
// next.next获取下一个元素
next = next.next;
nextIndex++;
return lastReturned.item;
}
}
}
我们遍历一个集合时,最常使用的方式就是增强for
循环了,即for(T t : 集合)
,那么它和迭代器有什么关系呢?
我们通过增强for
循环演示一段代码
public class Demo {
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "d", "e", "f");
for (String s : list) {
System.out.print(s + " ");
}
}
}
输出结果为:
下面我们看一下java对该类所编译出的class文件
从该class文件中我们可以看到,我们在java文件中编写的增强for循环在编译过程中被转换成了迭代器。
优点:
缺点:
纸上得来终觉浅,绝知此事要躬行。
————————我是万万岁,我们下期再见————————