[工作中的设计模式]迭代子模式Iterator
一、模式解析
迭代子模式又叫游标(Cursor)模式,是对象的行为模式。迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象
1、迭代子模式一般用于对集合框架的访问,常用的集合框架为list,set,map在实现的时候均可以支持迭代子模式
2、迭代子模式使用同意接口Iterator来完成对象的迭代,一般接口需要实现如下功能:first,next,isDone,currentItem等遍历接口
3、常用的集合框架根据访问需要,可以使用数组,链表等数据结构进行实现,使用迭代子模式可以屏蔽掉数据结构遍历时候产生的差异
4、在java中对结合的增强for循环依赖于迭代子模式实现
二、应用场景
迭代子模式在实际工作中基本无需自己进行编写,因为使用太过频繁,已经被jdk底层进行实现,所以我们使用集合框架时候直接使用就可以了,在这里,我们需要模拟ArrayList的基本实现和迭代子模式的实现。
ArrayList的基本结构为:
1、list内可以存放多个相同类型的元素,
2、元素可以重复存放
3、在jdk底层采用数组进行模拟,当存放数据容量超出数组,会对数组进行扩容,自动增加一部分容量,扩容算法为:(this.elementData.length*3)/2+1
4、继承了Iterator,可以使用迭代器进行遍历
三、场景代码
1、Iterator接口,定义迭代方法
package iterator.example; /** * 定义迭代器的接口 * @author zjl * @time 2016-2-3 * */ public interface Iterator<E> { public E first(); public E next(); public boolean isDone(); public int currentItem(); }
2、定义list接口,为了简化处理,此处直接继承迭代器接口
package iterator.example; public interface List<E> extends Iterator<E> { public int size(); public boolean isEmpty(); public E remove(int index); public int indexOf(E e); void add(E e); public E get(int index); }
3、定义Arraylist,实现各方法
package iterator.example; import java.util.Arrays; public class ArrayList<E> implements List<E>{ //初始化一个数组,来保存对象 Object[] elementData; private int size=0; //迭代器游标 private int cursor=0; //数组初始化 public ArrayList(int init){ elementData=new Object[init]; } //默认构造类型,默认初始化10个元素 public ArrayList(){ this(10); } @Override public int size() { return this.size; } @Override public boolean isEmpty() { return this.size==0; } @Override public void add(E e) { //如果list长度等于数组长度,那么将数组扩大 ensureCapacity(this.size+1); elementData[this.size++]=e; } @Override public E remove(int index) { RangeCheck(index); //移除指定位置的元素 E e= (E) elementData[index]; for(;index<this.size;index++){ elementData[index]=elementData[index+1]; } elementData[index--]=null; return e; } @Override public int indexOf(E e) { // TODO Auto-generated method stub return 0; } //判断是否超长,如果超长,则扩展数组 public void ensureCapacity(int minCapacity){ int oldCapacity=this.elementData.length; if(minCapacity>oldCapacity){ int newLength=(this.elementData.length*3)/2+1; Object[] oldDate=this.elementData; this.elementData=Arrays.copyOf(elementData, newLength); } } private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); } @Override public E first() { return (E) elementData[0]; } @Override public E next() { if(isDone()){ throw new IndexOutOfBoundsException("the list is done"); } return (E) elementData[cursor++]; } @Override public boolean isDone() { return cursor==size; } @Override public int currentItem() { // TODO Auto-generated method stub return cursor; } @Override public E get(int index) { RangeCheck(index); return (E) elementData[index]; } }
4、编写客户端程序
package iterator.example; public class Client { /** * @param args */ public static void main(String[] args) { List<String> list=new ArrayList<String>(); list.add("java"); list.add("c++"); list.add("python"); list.add("Object-c"); while(!list.isDone()){ System.out.println(list.next()); } } }
5、执行结果
java c++ python Object-c
四、解决差异问题
此时,虽然可以正常使用迭代器进行循环,但是很明显和jdk中的ArrayList迭代方式不一致,而且无法使用增强循环模式for..in,观察下jdk的实现,我们发现list并没有实现Iterator接口,而是使用了Iterable接口作为过渡,在Iterable中保存了Iterator的引用。
修改代码为List继承jdk的Iterable,注意,此处必须引用jdk的接口才能增强循环,修改ArrayList代码为,增加方法为:
@Override public Iterator<E> iterator() { return new Iterator<E>() { private int cursor=0; @Override public boolean hasNext(){ return cursor!=size; } @Override public E next() { return (E) elementData[cursor++]; } //仅仅为了遍历,此方法暂不实现 @Override public void remove() { } }; }
此时客户端可以使用与原生一样的迭代方法
Iterator<String> it=list.iterator(); while(it.hasNext()){ System.out.println(it.next()); } for(String s:list){ System.out.println(s); }
为什么不直接继承Iterator接口,反而使用过渡呢,原因大概如下:
针对集合进行迭代时候,需要使用游标标记当前迭代位置,如果多个位置同时对一个集合进行迭代的话,那么同时需要多个游标来标记当前已经迭代的位置,如果直接继承Iterator接口,那么游标必须保持在集合框架本身,必然造成迭代的冲突,所以使用Iterable,每次开始迭代时候,返回一个Iterator的实现方法,产生一个游标,这样多次迭代不会产生相互冲突。