给jdk写注释系列之jdk1.6容器(3)-Iterator设计模式
本系列:
给jdk写注释系列之jdk1.6容器(1)-ArrayList源码解析
给jdk写注释系列之jdk1.6容器(2)-LinkedList源码解析
前面讲了两种List,一种基于数组实现的ArrayList,一种基于链表实现的LinkedList,这两种list是我们工作中最常用到的List容器。当然数组和链表也是两种常见的基本数据结构,其他基本数据结构还有堆栈、队列、树等,对java容器的学习,也可以看做是对数据结构的学习和使用。
在ArrayList和LinkedList的分析中,都没有对容器遍历进行分析,前面说过迭代器相对独立和复杂,而且它又是一种非常经典的 设计模式 (说经典不过使用场景也就这一个…),这里开始对Iterator进行分析。
细心的童鞋会看到这边blog的标题叫做”设计模式”而不是前面的”源码解析”,是的这里会从设计模式层面出发,更偏重于怎么更好的进行与改善代码的设计。
1.统一接口
先想一个问题,现在有ArrayList和LinkedList两种容器,我们怎么实现容器的可替换性呢?什么叫做可替换性,就是我底层的容器实现可以随意改变,而对用户的使用不产生任何影响,说到这里应该都明白了就是要统一接口,用户只需要面向接口编程对不对(这里以及下面说的用户都是指开发者)。来看看ArrayList和LinkedList是不是这样做的(假装你还不知道的情况下,一起看吧^_^)?
ArrayList的定义:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
LinkedList的定义:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
从定义上可以看到ArrayList和LinkedList都实现了List接口,ok看下List接口的定义:
public interface List<E> extends Collection<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean addAll( int index, Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); E get( int index); E set( int index, E element); void add( int index, E element); E remove( int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator( int index); List<E> subList( int fromIndex, int toIndex); }
可以看到List中对容器的各种操作add、remove、set、get、size等进行了统一定义,同时List实现了Collection接口,继续看下Collection接口的定义(先不关心Iterator):
public interface Collection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); }<span style="font-weight: normal;"> </span>
有了这两个接口,对于ArrayList和LinkeList的操作是不是就可以这么写了呢?
Collection<String> collection = new ArrayList<String>(); collection.add("hello"); collection.add("java"); collection.remove("hello");
对于ArrayList的实现不满意,ok换成LinkedList实现,
Collection<String> collection = new LinkedList<String>(); collection.add("hello"); collection.add("java"); collection.remove("hello");
对于用户来说,add、remove等操作是没有任何影响的,好了,到这里了解了统一接口,面向接口编程的好处,接下来在思考另外一个问题,怎么给容器提供一种遍历方式。
2.Iterator设计思想
你可能会认为不就是遍历嘛,很好提供啊,ArrayList就用数组下标进行遍历,LinkedList就用指针next进行遍历,仔细想一下真的这么简单嘛?结合上一个问题,如果底层的容器实现变化了,用户的使用是不是也需要根据具体实现的数据结构来改变遍历方式呢?显然这样是不科学的吗,这么简单我讲上面的统一接口干嘛,对不对。。。
是的,需要统一一种遍历方式,提供一个统计遍历接口,而具体实现需要具体的容器自己根据自身数据结构特点来完成,而对于用户就只有一个接口而已,万年不变。于是就有了Iterator,接下来看下Iterator的实现吧。。。
先回过头去看Collection的定义,发现Collection 实现了一个Iterable 接口(早就发现了好嘛?),来看下的Iterable的定义,
/** Implementing this interface allows an object to be the target of * the "foreach" statement. * @since 1.5 */ public interface Iterable<T> { /** * Returns an iterator over a set of elements of type T. * * @return an Iterator. */ Iterator<T> iterator(); }
英文注释说,实现iterable接口的类可以使用“foreach”操作,然后并要求实现Iterator<T> iterator()方法,该方法返回一个Iterator接口对象,来看下Iterator接口,
public interface Iterator<E> { // 是否还有元素 boolean hasNext(); // 下一个元素 E next(); // 将迭代器返回的元素删除 void remove(); }
Iterator接口一共有三个方法,通过这三个方法就可以实现通用遍历了,ok,我们来试一下。
Collection<String> collection = new ArrayList<String>(); collection.add("hello"); collection.add("java"); Iterator<String> iterator = collection.iterator(); while (iterator.hasNext()) { System. out.println(iterator.next()); }
用户再也不用关心容器的底层实现了,不管你是数组还是链表还是其他什么,只需要用Iterator就可以进行遍历了。
到这里Iterator的设计原则和思路实际上已经讲完了,我们来整理下Iterator模式的几个角色:
1.迭代器Iterator接口。
2.迭代器Iterator的实现类,要求重写hasNext(),next(),remove()三个方法 。
3.容器统一接口,要求有一个返回Iterator接口的方法。
4.容器实现类,实现一个返回Iterator接口的方法。
3.自己实现容器的Iterator遍历
如果让我们自己来实现Iterator模式,应该怎么来实现呢,试一下。
Iterator接口:
public interface MyIterator { Object next(); boolean hasNext(); }
容器统一接口:
public interface MyCollection { void add(Object o); int size(); MyIterator iterator(); }
容器实现类和Iterator实现类(Iterator实现类为容器内部类):
public class MyArrayList implements MyCollection { private Object[] data ; private int size; public MyArrayList() { data = new Object[10]; } public void add(Object o) { if (size == data. length) { Object[] newData = new Object[data .length * 2]; System. arraycopy(data, 0, newData, 0, data.length ); data = newData; } data[size ] = o; size++; } public int size() { return size ; } @Override public MyIterator iterator() { return new Itr(); } private class Itr implements MyIterator { private int index = 0; @Override public boolean hasNext() { if (index >= size) { return false; } else { return true; } } @Override public Object next() { Object o = data[index ]; index++; return o; } } }
应用一下:
public class Test { public static void main(String[] args) { MyCollection c = new MyArrayList(); c.add( "t"); c.add( "s"); c.add( "t"); c.add( "d"); System. out.println(c.size()); MyIterator itr = c.iterator(); while (itr.hasNext()) { System. out.println(itr.next()); } } }
4 t s t d
没问题,很容易就实现了对不对,当然这里只是简单模拟,没有过多的追求效率和优雅的设计。
4.ArrayList的Iterator实现
下面来看一看ArrayList和LinkedList对Iterator接口的实现吧,ArrayList的Iterator实现封装在AbstractList中,看一下它的实现:
public Iterator<E> iterator() { return new Itr(); }<span style="font-weight: normal;"> </span>
和自己实现的一样,采用内部类实现 Iterator接口。
private class Itr implements Iterator<E> { // 将要next返回元素的索引 int cursor = 0; // 当前返回的元素的索引,初始值-1 int lastRet = -1; /** * The modCount value that the iterator believes that the backing * List should have. If this expectation is violated, the iterator * has detected concurrent modification. */ int expectedModCount = modCount; public boolean hasNext() { // 由于cursor是将要返回元素的索引,也就是下一个元素的索引,和size比较是否相等,也就是判断是否已经next到最后一个元素 return cursor != size(); } public E next() { checkForComodification(); try { // 根据下一个元素索引取出对应元素 E next = get( cursor); // 更新lastRet为当前元素的索引,cursor加1 lastRet = cursor ++; // 返回元素 return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { // remove前必须先next一下,先取得当前元素 if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList. this.remove(lastRet ); // 确保lastRet比cursor小、理论上永远lastRet比cursor小1 if (lastRet < cursor) // 由于删除了一个元素cursor回退1 cursor--; // 重置为-1 lastRet = -1; expectedModCount = modCount ; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
ArrayList的Iterator实现起来和我们自己实现的大同小异,很好理解。
有一点需要指出的是这里的checkForComodification 检查,之前ArrayList中留了个悬念,modCount这个变量到底是做什么的,这里简单解释一下:迭代器Iterator允许在容器遍历的时候对元素进行删除,但是有一点问题那就是当多线程操作容器的时候,在用Iterator对容器遍历的同时,其他线程可能已经改变了该容器的内容(add、remove等操作),所以每次对容器内容的更改操作(add、remove等)都会对modCount+1,这样相当于乐观锁的版本号+1,当在Iterator遍历中remove时,检查到modCount发生了变化,则马上抛出异常ConcurrentModificationException,这就是Java容器中的 “fail-fast”机制 。
5.LinkedList的Iterator实现
LinkedList的Iterator实现定义在AbstracSequentialtList中,实现在AbstractList中,看一下:
AbstracSequentialtList的定义:
public Iterator<E> iterator() { return listIterator(); }
AbstractList的实现:
public ListIterator<E> listIterator() { return listIterator(0); } public ListIterator<E> listIterator(final int index) { if (index<0 || index>size()) throw new IndexOutOfBoundsException( "Index: "+index); return new ListItr(index); }
看一下ListItr实现:
private class ListItr implements ListIterator<E> { // 最后一次返回的节点,默认位header节点 private Entry<E> lastReturned = header; // 将要返回的节点 private Entry<E> next ; // 将要返回的节点index索引 private int nextIndex; private int expectedModCount = modCount; ListItr( int index) { // 索引越界检查 if (index < 0 || index > size) throw new IndexOutOfBoundsException( "Index: "+index+ ", Size: "+size ); // 简单二分,判断遍历的方向 if (index < (size >> 1)) { // 取得index位置对应的节点 next = header .next; for (nextIndex =0; nextIndex<index; nextIndex++) next = next .next; } else { next = header ; for (nextIndex =size; nextIndex>index; nextIndex --) next = next .previous; } } public boolean hasNext() { // 根据下一个节点index是否等于size,判断是否有下一个节点 return nextIndex != size; } public E next() { checkForComodification(); // 遍历完成 if (nextIndex == size) throw new NoSuchElementException(); // 赋值最近一次返回的节点 lastReturned = next ; // 赋值下一次要返回的节点(next后移) next = next .next; // 将要返回的节点index索引+1 nextIndex++; return lastReturned .element; } public boolean hasPrevious() { return nextIndex != 0; } // 返回上一个节点(双向循环链表嘛、可以两个方向遍历) public E previous() { if (nextIndex == 0) throw new NoSuchElementException(); lastReturned = next = next. previous; nextIndex--; checkForComodification(); return lastReturned .element; } public int nextIndex() { return nextIndex ; } public int previousIndex() { return nextIndex -1; } public void remove() { checkForComodification(); // 取出当前返回节点的下一个节点 Entry<E> lastNext = lastReturned.next ; try { LinkedList. this.remove(lastReturned ); } catch (NoSuchElementException e) { throw new IllegalStateException(); } // 确认下次要返回的节点不是当前节点,如果是则修正 if (next ==lastReturned) next = lastNext; else // 由于删除了一个节点,下次要返回的节点索引-1 nextIndex--; // 重置lastReturned为header节点 lastReturned = header ; expectedModCount++; } public void set(E e) { if (lastReturned == header) throw new IllegalStateException(); checkForComodification(); lastReturned.element = e; } public void add(E e) { checkForComodification(); lastReturned = header ; addBefore(e, next); nextIndex++; expectedModCount++; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }<span style="font-weight: normal;"> </span>
LinkedList的Iterator实现就分析完了,从这里可以看出数组和链表的遍历方式不同,但对于用户统一使用Iterator迭代器进行遍历器,做到只需面对接口编程,这也是Iterator的最终要做到的。
最后啰嗦一句,Iterator是比较简单的一种设计模式,使用场景也仅限于此,不要纠结于Iterator的应用而是要明白其设计思想,同时要对一些常用数据结构应该要了解掌握。
Iterator 完!
</div> </div> </div>