小白學Java:迭代器原來是這么回事


小白學Java:迭代器原來是這么回事

前文傳送門:Enumeration
上一篇,我們談到了那個古老的迭代器Enumeration,還談到了取代他的新迭代器——Iterator。相比於以往,這個新物種又有哪些優點呢?

迭代器這個詞,在沒查找許多資料之前,我只知道個大概,我知道它可以用來遍歷集合,但是至於它其中的奧妙,並沒有做深究。本篇文章關於Iterator迭代器做了小小的總結,鞏固學習,如果有理解錯誤,或敘述不當之處,還望大家評論區批評指針。

迭代器概述

官方文檔對Iterator的解釋是:

  • 它取代了Enumeration。
  • 它作用於任何一個Collection。
  • 它增加了remove的功能。
  • 它優化了方法命名。

不行不行,這描述也太簡略了,我繼續查找資料:

  • 迭代器本身是個對象,創建迭代器的代價很小,通常被稱為輕量級對象
  • 迭代器其實也是一種設計模式,它提供了一種方法順序訪問一個聚合對象中的各個元素,但又不暴露該對象的內部表示。

迭代器設計模式

針對以上種種,我充滿了好奇,於是在復雜的繼承關系里畫了又畫,最終才漸漸理清集合中所謂迭代器模式的體現,暫時以ArrayList為例:

  • 定義了一個迭代器的接口Iterator,里面定義了迭代器的功能,但並沒有提供實現。
  • 定義了一個聚集接口Iterable,里面的iterator()抽象方法,表明返回一個針對類型T的迭代器。此時將Iterable和Iterator聯系了起來。
  • 我們知道聚集接口被許多接口所擴展,定義相同的方法,Collection接口就是其一,所以說,迭代器針對於所有集合都有效。
  • 我們以集合的具體類ArrayList為例,暫時忽略之間的繼承關系,ArrayList顯然提供了抽象方法iterator()的具體實現,我們查看源碼發現,它的返回值是一個Itr對象。
  • 這個Itr其實是ArrayList的一個內部類,它提供了迭代器接口的具體實現(當然不一定是內部類),這樣所有東西都聯系在了一起。

Iterator定義的方法

  • hasNext():boolean 判斷下一個元素還有沒有,有就是true。
  • next(): E 返回序列中的下一個元素。

通過查看源碼,我發現,在這個Itr這個實現類中,定義了兩個指針:cursorlastRet。(還有個屬性為expectedModCount初始化為ArrayList的版本號modCount,這部分與fail-fast機制相關,之后會再提)而cursor初始為0,與專門用來和集合元素數目size做比較的。而lastRet初始化為-1,如果成功執行next操作,將會加1變成0,也就是上面說的“下一個元素”可想而知,可以把lastRet認為是初始化為第一個元素之前的指針,和將要返回的值的索引相同,這樣會好記一些。

除了上面兩個方法,JDK1.8新增了兩個方法,也是體現處它與老迭代器不同的新優勢:支持了刪除操作。

  • remove():void 將新近返回的元素刪除。
    需要注意的是:remove方法沒有新近返回的元素,也就是說lastRet<0,會拋出異常。如果移除成功,讓cursor往回退一格,lastRet重置為-1。

  • forEachRemaining(Consumer<? super E> consumer):void 這個是JDK1.8中Iterator新增的默認方法:對剩余的元素執行指定的操作
    可能不太好理解:我們通過測試來說明一下:

    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    //創建一個Iterator對象
    Iterator<Integer> it = list.iterator();
    //返回第一個值
    System.out.print(it.next()+" ");

測試結果很明顯,只輸出了第一個元素:1。
我們繼續在原代碼的基礎上我們的新方法:

    it.forEachRemaining(new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            System.out.print(integer+" ");
        }
    });

測試結果為:1 2 3,在原來的基礎上,把剩下的元素都打印了出來。而這個新增的方法,其實和我們熟悉的這個是一樣的:

    while(it.hasNext()){
        System.out.print(it.next()+" ");
    }

值得一提的是:我們之前學習的增強for循環,在底層其實就是運用了Iterator,我通過IDE的debug調試功能,發現在調用運行到增強for循環時,自動調用了集合的iterator()方法,返回了一個Iterator的實現類實例。

迭代器:統一方式

通過對Iterator中定義方法的學習,我們大概知道了迭代器的用途,就是從前向后一個一個遍歷元素,而無視其內部結構。欸,遍歷我都懂,可無視結構在哪里體現啊?別急,下面來看一個例子,讓我們無視兩個不同集合的結構:

首先我們定義一個方法,它可以接收一個迭代器對象:

    public static void display(Iterator<?> T){
        while(T.hasNext()){
            System.out.print(T.next());
        }
    }

然后我們創建兩個不一樣的集合,一個是ArrayList,一個是HashSet,本身是無序的,我們接下來應該會做相應的源碼學習。

        //ArrayList 有序
        List<String> list = new LinkedList<>();
        list.add("天");
        list.add("喬");
        list.add("巴");
        list.add("夏");
        //HashSet 無序
        Set<Integer> set = new HashSet<>();
        set.add(11);
        set.add(22);
        set.add(33);
        set.add(44);
        display(list.iterator());//天 喬 巴 夏
        System.out.println();
        display(set.iterator());//33 22 11 44

可以看出來,兩個不同集合的迭代器傳入display方法之后,都能用一種相同的方式訪問集合中的元素。
通過上面的一頓分析,我們可以確定,迭代器這玩意兒,統一了訪問容器的方式

Iterator的總結

  • Iterator支持從前向后順次遍歷,統一了對不同集合里元素的操作
  • 還在Enumeration的基礎上,簡化了命名,而且Enumeration並不是對所有集合都適用。
  • 四大技能增刪改查,雖然支持刪和查,但不支持增和改。
  • 支持單向迭代,某些情況下不是很靈活。(ListIterator可以支持雙向,但只支持List類型)

最后,關於迭代器,還有一部分內容,在日后會做總結。
參考資料:《大話設計模式》、《Java編程思想》


免責聲明!

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



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