Java 迭代器介紹

Java 迭代器介紹

迭代器模式

迭代器模式是一個典型的設計模式,提供一種方法訪問一個容器對象中各個元素,而又不暴露該對象的內部細節。因為屏蔽了細節,可以針對不同實現的容器,提供一致的標準化的訪問方法。

迭代器模式有四個部分組成:

  1. 抽象容器 Aggregate
  2. 具體容器 ConcreteAggregate
  3. 抽象迭代器 Iterator
  4. 迭代器實現 ConcreteIterator
迭代器模式類圖

Java 迭代器

框架

Java 中的容器接口 Collection 繼承了迭代器接口 Iterable,迭代器接口中的 iterator() 方法返回 Iterator 類型對象。這里 Collection 相當于迭代器模式中的抽象容器 Aggregate;Iterator 相當于迭代器模式中的抽象迭代器 Iterator。

public interface Collection<E> extends Iterable<E> {
    Iterator<E> iterator();
    // 省略其他
}

public interface Iterable<T> {
    Iterator<T> iterator();
}

public interface Iterator<E> {
    // 判斷是否還有下一個元素
    boolean hasNext();
    // 獲取下一個元素
    E next();
    // 移除一個元素
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

java 中 List 、Set、Queue 容器都會繼承實現 Collection 類,提供迭代器接口,我們以 List 為例看一下迭代器的實現。

List 容器框架

List 的迭代器實現

我們可以看一下 ArrayList 中迭代器的實現:

  1. Itr 實現了 hasNext、next、remove 三個方法
  2. 遍歷靠下標 cursor 的遞增,其實現和我們自己遍歷數組一樣(迭代器好處是屏蔽細節、標準化)
  3. remove 之后 lastRet 置為 -1,調用一次 next 后只能調用一次 remove,否則會拋異常
  4. 調用 next 之前也要調用 hasNext 判斷,否則 cursor 大于等于 size 會拋異常
  5. 迭代器遍歷過程中,只能使用其 remove 方法移除元素,如果使用了 List 本身的 add、remove 方法,會導致下次調用 next 方法時拋出異常。原因是迭代器 Itr 里保存了 List 修改次數 modCount 的值到局部變量 expectedModCount 里,不通過迭代器的修改會導致兩者不一致,導致 checkForComodification 方法拋異常。
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.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;

        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();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
}

ArrayList 中的迭代器在容器變更后會失效,那么有沒有不失效的呢?我們可以看看 CopyOnWriteArrayList 的迭代器實現:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

    private static class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array **/
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

        public boolean hasNext() {
            return cursor < snapshot.length;
        }

        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }
    }    
}

CopyOnWriteArrayList 迭代器 COWIterator 遍歷的是創建迭代器時的數據快照,這樣后續容器的元素增加和減少,就不會導致迭代器的失效了。請注意這里并沒有記錄容器元素的實際大小,容器元素數目完全取決于數組 snapshot 的長度。我們知道在 ArrayList 中,為了減少擴容次數,擴容時數組長度會比實際所需的長,導致數組長度和容器元素個數不一致。CopyOnWriteArrayList 沒有這個問題嗎?確實是的,我們看一下其添加元素時,擴容的源代碼:

  1. 每次擴容,新數組長度等于老數組長度加1,長度剛剛好。
  2. 每次添加元素,都需要擴容,效率低下。
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

擴展迭代器 ListIterator

List 容器中,不僅提供了普通迭代器 Iterator 的實現,還有一個功能更強的迭代器 ListIterator:

  1. 除了正向遍歷之外,還提供了反向遍歷的功能
  2. 除了 remove 之外,提供了 set 和 add,當然通過迭代器的這些方法修改數據,都不應該導致迭代器失效
public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();
    E next();
    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();
    void remove();
    void set(E e);
    void add(E e);  
}

總結

迭代器模式,體現了標準化遍歷容器,屏蔽內部實現細節的編程思想。

普通迭代器在遇到容器修改時會失效,但是一些線程安全類實現的迭代器比如 COWIterator 是不會因容器修改而失效的。

參考文獻

  1. 迭代器模式
  2. Java 8 被動迭代式特性介紹
  3. Java 容器框架
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容

  • java筆記第一天 == 和 equals ==比較的比較的是兩個變量的值是否相等,對于引用型變量表示的是兩個變量...
    jmychou閱讀 1,511評論 0 3
  • Java源碼研究之容器(1) 如何看源碼 很多時候我們看源碼, 看完了以后經常也沒啥收獲, 有些地方看得懂, 有些...
    駱駝騎士閱讀 1,002評論 0 22
  • 1 場景問題# 1.1 工資表數據的整合## 考慮這樣一個實際應用:整合工資表數據。 這個項目的背景是這樣的,項目...
    七寸知架構閱讀 2,572評論 0 53
  • 夜已深,雨后的月亮慢慢掰開云層露出真容,而我習慣在深夜思念您的心依舊難以平靜,不知您是否已經入睡,不知您的那篇天空...
    娜因有您閱讀 234評論 0 0
  • 數算恩福 1.感恩媽媽,我讀初二那年,視力變的模糊,看不清黑板上的字,那時我的一位堂哥有去縣城,媽媽就叫他幫我買眼...
    lrryy閱讀 153評論 0 1