小白學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這個實現類中,定義了兩個指針:cursor
和lastRet
。(還有個屬性為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編程思想》