1. 程式人生 > >迭代器模式-統一集合的遍歷方式

迭代器模式-統一集合的遍歷方式

> **公號:碼農充電站pro** > **主頁:** 今天來介紹**迭代器模式**(`Iterator Design Pattern`),它還有另一個名字,叫作**遊標模式**(`Cursor Design Pattern`)。 ### 1,遍歷集合元素 現在的高階語言(比如 `C++`,`Java`,`Python` 等)都支援很多種**集合**(比如 `List`,`Map`,`Set` 等),用於儲存物件。 同時這些高階語言也都原生支援了**迭代器**,這使得**遍歷**集合變得非常簡單。 下面我們來看下,如果不使用迭代器,如何遍歷集合。 以 `Java ArrayList` 為例,建立 `list`,並加入 5 個元素: ```java ArrayList list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); ``` 用 for 迴圈遍歷 `list`: ```java for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } ``` 如果不用迭代器,只能使用 `get` 方法,將集合中的元素一個個取出。 如果使用迭代器遍歷元素,就可以像下面這樣: ```java Iterator i = list.iterator(); while (i.hasNext()) { System.out.println(i.next()); } ``` 先用 `iterator` 方法返回迭代器,再用 `hasNext` 方法檢視迭代器中是否還有元素,如果有元素則用 `next` 方法取出元素。 更簡單的方式是使用 `forEeah` 迴圈: ```java for (Object i: list) { System.out.println(i); } ``` > **forEach 迴圈**(對迭代器的一種包裝)是 **Java 5** 中引入的遍歷集合的方式,這種方式不再需要獲取**迭代器**,甚至不需要知道所遍歷的是哪種資料結構,也不需知道其中儲存的是什麼型別的資料。 ***Java 集合框架與迭代器*** Java 中的 `Set`,`List`,`Queue` 都實現了 `Collection` 介面,該介面中的 `iterator` 方法返回一個迭代器,用於遍歷集合中的元素。 這使得所有的 `Collection` 物件的遍歷都變得非常簡單,使用 `forEach` 迴圈即可: ```java for (Object o: collection) { // 遍歷元素 } ``` ### 2,迭代器模式 **迭代器模式提供了一種方法,用於遍歷集合物件中的元素,而又不暴露其內部的細節**。 一般的**迭代器都要實現一個迭代器介面**,該介面至少包含兩個方法,即 `hasNext` 和 `next`: ```java public interface Iterator { boolean hasNext(); Object next(); } ``` `hasNext` 方法用於檢視集合中是否還有下一個元素;`next` 方法用於返回集合中的下一個元素,並移動指向元素的遊標。 > **Java** 中的 **Iterator** 介面中還有一個 `remove` 方法,其實這個方法的必要性並不大,所以它的預設實現是丟擲 `UnsupportedOperationException` 異常。因此,對於 `remove` 方法,不需要給予太多的關注。 這使得每個集合中的元素的訪問方式都是統一的。 一個完整的迭代器模式一般會包含**集合**與**迭代器**兩部分,為了達到**基於介面而非實現程式設計**的原則,還抽象出了兩個介面,其類圖如下: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2021010317052726.png?) **Collection** 中的 `iterator` 方法用於返回當前物件的迭代器,從而遍歷集合中的元素。 迭代器模式將集合物件的遍歷操作從集合類中拆分出來,放到迭代器類中,使得兩者的職責更加單一。 > 這其實用到了**單一職責原則**:一個類應該只有一個引起變化的原因。 ### 3,迭代器不支援增刪 迭代器的目的是為了方便元素的**遍歷**,而如果在遍歷的過程中**增刪元素**,則會導致元素**重複遍歷**或者**遍歷不到**。 下面來看下這種錯誤是如何產生的。 假設一個列表中有 `a`,`b`,`c`,`d` 四個元素,我們從前往後遍歷。剛開始時,遊標 `cursor` 指向 `a`: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210103174805252.png) 當遍歷到 `b` 時,`cursor` 指向 `b`: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210103174858699.png) ***遍歷時刪除元素*** 如果此時將 `a` 元素刪除,那麼所有其它元素都會前移一個位置: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210103182519453.png) 從而,這時的遊標就會指向 `c`,這就會導致 `b` 沒有被遍歷到。 ***遍歷時增加元素*** 如果在遍歷到 `b` 的時候,在表頭增加一個元素 `x`,那麼所有的元素都會後移一個位置: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210103182053225.png) 從而,這時的遊標依然是指向 `a`,這就會導致 `a` 被重複遍歷了。 因此,在使用迭代器遍歷元素的時候都會禁止增刪元素。 ***Java 如何禁止增刪元素*** 為了禁止在遍歷時增刪元素,Java 的做法是,在遍歷元素時會進行 `checkForXXX` 操作,目的是檢查是否有元素增刪,如果有增刪的情況,則丟擲異常。 ### 4,總結 迭代器模式是為了方便元素的遍歷,它統一了集合的遍歷方式。它將元素的遍歷操作從集合中拆分出來,從而使得兩者得以解耦。 大部分高階語言都原生支援迭代器,這使得開發人員可以專注於業務實現,而不用過多的關心底層實現。 如果在迭代器的遍歷過程中增刪元素,則會導致元素的遍歷發生錯誤,因此迭代器中一般不支援增刪元素。 (本節完。) --- **推薦閱讀:** [裝飾者模式-動態的包裝原有物件的行為](https://www.cnblogs.com/codeshell/p/14210116.html) [命令模式-將請求封裝成物件](https://www.cnblogs.com/codeshell/p/14214705.html) [介面卡模式-讓不相容的介面得以適配](https://www.cnblogs.com/codeshell/p/14228610.html) [外觀模式-簡化子系統的複雜性](https://www.cnblogs.com/codeshell/p/14234062.html) [模板方法模式-封裝一套演算法流程](https://www.cnblogs.com/codeshell/p/14239615.html) --- *歡迎關注作者公眾號,獲取更多技術乾貨。* ![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic