迭代器模式
迭代器模式又叫做遊標(Cursor)模式,其作用是提供一種方法訪問一個容器元素中的各個物件,而又不暴露該物件的內部細節。
迭代器模式結構
迭代器模式由以下角色組成:
1、迭代器角色
負責定義訪問和遍歷元素的介面
2、具體迭代器角色
實現迭代器介面,並要記錄遍歷中的當前位置
3、容器角色
負責提供建立具體迭代器角色的介面
4、具體容器角色
實現建立具體迭代器角色的介面,這個具體迭代器角色與該容器的結構相關
迭代器模式在JDK中的應用及解讀
迭代器模式就不自己寫例子了,直接使用JDK中的例子。為什麼我們要使用迭代器模式,思考一個問題,假如我有一個ArrayList和一個LinkedList:
List<Integer> arrayList = new ArrayList<Integer>(); arrayList.add(1); arrayList.add(2); List<Integer> linkedList = new LinkedList<Integer>(); linkedList.add(3); linkedList.add(4);
如何去遍歷這兩個List相信每個人都很清楚:
System.out.println("ArrayList:"); for (int i = 0; i < arrayList.size(); i++) System.out.print(arrayList.get(i) + "\t"); System.out.println("\nLinkedList:"); for (int i = 0; i < linkedList.size(); i++) System.out.print(linkedList.get(i) + "\t");
執行結果為:
ArrayList: 1 2 LinkedList: 3 4
這是因為恰好,我們知道ArrayList和LinkedList的訪問方式,有些喜歡研究的人知道ArrayList和LinkedList的內部結構,但如果現在我給你一個HashSet:
HashSet<Integer> hashSet = new HashSet<Integer>(); hashSet.add(5); hashSet.add(6);
將如何遍歷?可能你還以為可以使用類似List的遍歷方式,不過很遺憾,HashSet中根本沒有提供get方法。
這時候就輪到迭代器出場了,不管是什麼資料結構,不管你聽過還是沒聽過,不管你見過還是沒見過,只要它實現了Iterable介面,都可以用類似的方式去遍歷,我把ArrayList、LinkedList、HashSet的遍歷寫在一起:
Iterator<Integer> iter = null; System.out.println("ArrayList:"); iter = arrayList.iterator(); while (iter.hasNext()) { System.out.print(iter.next() + "\t"); } System.out.println("\nLinkedList:"); iter = linkedList.iterator(); while (iter.hasNext()) { System.out.print(iter.next() + "\t"); } System.out.println("\nHashSet:"); iter = hashSet.iterator(); while (iter.hasNext()) { System.out.print(iter.next() + "\t"); }
看一下執行結果:
ArrayList: 1 2 LinkedList: 3 4 HashSet: 5 6
看到這就遍歷出來ArrayList、LinkedList、HashSet了,以後遇到一個集合,只要實現了iterable介面,也都可以類似這麼遍歷。這就是開頭迭代器模式的定義說的,開發者不需要知道集合中如何去遍歷的細節,只管用類似的遍歷方法就好了。
Iterable介面和Iterator介面
這兩個都是迭代相關的介面,可以這麼認為,實現了Iterable介面,則表示某個物件是可被迭代的;Iterator介面相當於是一個迭代器,實現了Iterator介面,等於具體定義了這個可被迭代的物件時如何進行迭代的。參看Iterable介面的定義:
public interface Iterable<T> { /** * Returns an iterator over a set of elements of type T. * * @return an Iterator. */ Iterator<T> iterator(); }
這樣物件就可以使用這個類的迭代器進行迭代了,一般Iterable和Iterator介面都是結合著一起使用的。為什麼一定要實現Iterable介面而不直接實現Iterator介面了呢,這個問題我也是在自己寫了ArrayList和LinkedList的實現之後才想明白的,這麼做確實有道理:
因為Iterator介面的核心方法next()或者hasNext()都是依賴於迭代器的當前迭代位置的。如果Collection直接實現Iterator介面,勢必導致集合物件中包含當前迭代位置的資料,當集合在不同方法間被傳遞時,由於當前迭代位置不可預置,那麼next()方法的結果會變成不可預知的。除非再為Iterator介面新增一個reset()方法,用來重置當前迭代位置。但即使這樣,Collection也同時只能存在一個當前迭代位置。而Iterable,每次呼叫都返回一個從頭開始計數的迭代器,多個迭代器時互不干擾。
可能這麼解釋不是很明白,再解釋明白一點,我自己寫的一個ArrayList,如果直接實現Iterator介面,那麼勢必是這麼寫的:
public class ArrayList<E> implements List<E>, Iterator<E>, RandomAccess, Cloneable, Serializable { /** * 序列化ID */ private static final long serialVersionUID = -5786598508477165970L; private int size = 0; private transient Object[] elementData = null; public E next() { ... } public boolean hasNext() { ... } ... }
這麼問題就來了,如果一個ArrayList例項被多個地方迭代,next()方法、hasNext()直接操作的是ArrayList中的資源,假如我在ArrayList中定義一個迭代位置的變數,那麼對於不同呼叫處,這個迭代變數是共享的,執行緒A迭代的時候將迭代變數設定成了第5個位置,這時候切換到了執行緒B,對於執行緒B來講,就從第5個位置開始遍歷此ArrayList了,根本不是從0開始,如何正確迭代?
實現Iterable介面返回一個Iterator介面的例項就不一樣了,我為自己寫的ArrayList定義一個內部類:
public class ArrayListIterator implements Iterator<E> { int iteratorPostion = 0; /** * 判斷是否後面還有元素 */ @Override public boolean hasNext() { if ((iteratorPostion + 1) > size) return false; return true; } /** * 返回之前一個元素的引用 */ @Override public E next() { return (E)elementData[iteratorPostion++]; } ... }
每次都返回一個返回一個ArrayListIterator例項出去:
/** * 返回一個ArrayList的迭代器,可以通過該迭代器遍歷ArrayList中的元素 */ public Iterator<E> iterator() { return new ArrayListIterator(); }
這就保證了,即使是多處同時迭代這個ArrayList,依然每處都是從0開始迭代這個ArrayList例項的。
迭代器模式的優缺點
優點
1、簡化了便利方式,對於物件集合的遍歷,還是比較麻煩的,對於陣列或者有序列表,我們還可以通過下標來獲取,但使用者需要在對集合很瞭解的情況下,才能自行遍歷物件(有時即使你瞭解了集合,還未必能直接遍歷,比如上面的HashSet就沒有提供get方法)。而引入了迭代器方法後,使用者用起來就簡單地多了
2、可以供多種遍歷方式,比如對於有序列表,可以正向遍歷也可以倒序遍歷,只要迭代器實現得好
3、封裝性好,使用者只需要得到迭代器就可以遍歷,而對於遍歷演算法則不用去關心
缺點
對於比較簡單的遍歷(陣列或者有序列表),使用迭代器方式遍歷較為繁瑣而且遍歷效率不高,使用迭代器的方式比較適合那些底層以連結串列形式實現的集合