Java設計模式8:迭代器模式


迭代器模式

迭代器模式又叫做游標(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、封裝性好,用戶只需要得到迭代器就可以遍歷,而對於遍歷算法則不用去關心

缺點

對於比較簡單的遍歷(數組或者有序列表),使用迭代器方式遍歷較為繁瑣而且遍歷效率不高,使用迭代器的方式比較適合那些底層以鏈表形式實現的集合

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM