Java 迭代器的程式碼實例詳解
一、摘要
迭代器模式是與集合共生共死的。一般來說,我們只要實作一個容器,就需要同時提供這個容器的迭代器。使用迭代器的好處是:封裝容器的內部實作細節,對於不同的集合,可以提供統一的遍歷方式,簡化客戶端的存取和取得容器內資料。在此基礎上,我們可以使用 Iterator 完成對集合的遍歷,此外,for 循環和foreach 語法也可以用於遍歷集合類別。 ListIterator 是容器 List容器族特有的雙向迭代器。本文重點主要包括:
迭代器模式
#Iterator 迭代器與Iterable 介面
迭代器模式是與集合共生共死的。
- 一般來說,我們只要實作一個容器,就需要同時提供這個容器的迭代器,就像Java 中的Collection (List、
- Set
等) ,這些容器都有自己的迭代器。假如我們要實作一個新的容器,當然也需要引入迭代器模式,給我們的容器實作一個迭代器。使用迭代器的好處是:封裝容器的內部實作細節,對於不同的集合,可以提供統一的遍歷方式,簡化客戶端的存取和取得容器內資料。
但是,由於容器與迭代器的關係太密切了,所以大多數語言在實現容器的同時也提供了相應的迭代器,並且在絕大多數情況下,這些語言所提供的容器和迭代器都可以滿足我們的需求。所以,現實中需要我們自己去實作迭代器模式的場景還是比較少見的,我們常常只需要使用語言中已有的容器和迭代器就可以了。 1、定義與結構
定義 迭代器(Iterator)模式,又叫做遊標( Cursor)模式。 GOF給出的定義為:提供一個方法來存取一個容器(container)物件中的各個元素,而又不需揭露該容器物件的內部細節。 從定義可見,
迭代器模式是為容器而生。我們知道,對容器物件的存取必然涉及到遍歷演算法。你可以一股腦的將遍歷方法塞到容器物件中去,或者,根本不去提供什麼遍歷演算法,讓使用容器的人自己去實現。這兩種情況好像都能夠解決問題。然而,對於前一種情況,容器承受了過多的功能,它不僅要負責自己「容器」內的元素維護(增、刪、改、查等),而且還要提供遍歷自身的介面;而且最重要的是, 由於遍歷狀態保存的問題,不能對同一個容器物件同時進行多個遍歷,並且還需增加reset 操作
###。 ######第二種方式倒是省事,卻又將容器的內部細節暴露無遺。 ##################################迭代器模式角色組成######## ### ###迭代器角色(Iterator):###### 迭代器角色###### 負責定義存取與遍歷元素的介面######;###### ##### ##特定迭代器角色(Concrete Iterator):###### 特定迭代器角色###### 要實作迭代器介面######,並要###### 記錄遍歷中的目前位置######;###### ######容器角色(Container): ###### 容器角色####### 負責定義建立特定迭代器角色的接口######;###特定容器角色(Concrete Container): 具體容器角色 實作建立具體迭代器角色的介面## —— 這個 特定迭代器角色 與該 容器的結構相關。
-
#結構圖
#迭代器模式在客戶端與容器之間加入了迭代器角色。迭代器角色的加入,就可以很好的避免容器內部細節的暴露,而且也使得設計符合 單一職責原則。 特別需要注意的是,在迭代器模式中,具體迭代器角色和具體容器角色是耦合在一起的 — — 遍歷演算法是與容器的內部細節緊密相關的。為了使客戶程式從與具體迭代器角色耦合的困境中脫離出來,避免具體迭代器角色的更換給客戶程式帶來的修改,迭代器模式抽象化了具體迭代器角色,使得客戶程式更具一般性和重用性,這被稱為 多態迭代 。
-
適用性 1.
存取一個容器物件的內容而無需暴露它的內部表示;# 2.
支援對容器物件的多種遍歷; 3.
為遍歷不同的容器結構提供一個統一的介面 ( 即,支援多型迭代 )。
- 2、範例
由於迭代器模式本身的規定比較鬆散,所以具體實作也就五花八門,我們在此僅舉一例。在舉例前,我們先來列舉一下迭代器模式的實作方式。 迭代器角色定義了遍歷的接口,但是沒有規定由誰來控制迭代。 在Java Collection 框架中,是由客戶程式來控制遍歷的進程,被稱為 外部迭代器;還有一種實作方式便是由迭代器本身來控制迭代,稱為
內部迭代器-
。外部迭代器要比內部迭代器靈活、強大,而且內部迭代器在Java 語言環境中,可用性很弱;在迭代器模式中沒有規定誰來實作遍歷演算法,好像理所當然的要在迭代器角色中實現。
因為既便於一個容器上使用不同的遍歷演算法,也便於將一種遍歷演算法套用到不同的容器。但是這樣就破壞掉了容器的封裝- 容器角色就要公開自己的私有屬性,在Java 中便意味著向其他類別公開了自己的私有屬性;# 那我們把它放到容器角色裡來實現好了,這樣,迭代器角色就被架空為僅僅存放一個遍歷當前位置的功能。但是遍歷演算法便和特定的容器緊緊綁在一起了。而在 Java Collection 框架中,提供的具體迭代器角色是定義在容器角色中的
內部類別,這樣便保護了容器的封裝。但是同時容器也提供了遍歷演算法接口,並且你可以擴展自己的迭代器。
我們來看下Java Collection 中的迭代器的實作:
-
迭代器模式的使用
//迭代器角色,仅仅定义了遍历接口public interface Iterator<E> { boolean hasNext(); E next(); void remove(); }//容器角色,这里以 List 为例,间接实现了 Iterable 接口public interface Collection<E> extends Iterable<E> { ... Iterator<E> iterator(); ... } public interface List<E> extends Collection<E> {} //具体容器角色,便是实现了 List 接口的 ArrayList 等类。为了突出重点这里指罗列和迭代器相关的内容 public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {…… //这个便是负责创建具体迭代器角色的工厂方法public Iterator<E> iterator() { return new Itr(); } //具体迭代器角色,它是以内部类的形式出来的。 AbstractList 是为了将各个具体容器角色的公共部分提取出来而存在的。 //作为内部类的具体迭代器角色 private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; //集合迭代中的一种“快速失败”机制,这种机制提供迭代过程中集合的安全性. ArrayList 中存在 modCount 属性,增删操作都会使 modCount++, //通过两者的对比,迭代器可以快速的知道迭代过程中是否存在 list.add() 类似的操作,存在的话快速失败! int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public Object next() { checkForComodification(); //快速失败机制 try { Object next = get(cursor); lastRet = cursor++; return next; } catch(IndexOutOfBoundsException e) { checkForComodification(); //快速失败机制 throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); //快速失败机制 try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; //快速失败机制 } catch(IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } //快速失败机制 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); //抛出异常,迭代终止 } }
登入後複製
客戶程式要先得到具體容器角色,然後再透過具體容器角色得到具體迭代器角色。這樣便可以使用具體迭代器角色來遍歷容器了…
3、適用情況 我們可以看出迭代器模式給容器的應用帶來以下好處:
1) 支援以不同的方式遍歷一個容器角色。根據實作方式的不同,效果上會有差別(例如,List 中的 iterator 和 listIterator)。
### 2) ###簡化了容器的介面。 ###但是在 Java Collection 中為了提升可擴充性,容器還是提供了遍歷的介面。 ###3) 简化了遍历方式。对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于 哈希表 来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。
4) 可以提供多种遍历方式。比如,对于有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
5) 对同一个容器对象,可以同时进行多个遍历。因为遍历状态是保存在每一个迭代器对象中的。
6) 封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
7) 在 Java Collection 中,迭代器提供一种快速失败机制 ( ArrayList是线程不安全的,在ArrayList类创建迭代器之后,除非通过迭代器自身remove或add对列表结构进行修改,否则在其他线程中以任何形式对列表进行修改,迭代器马上会抛出异常,快速失败),防止多线程下迭代的不安全操作。
由此,也可以得出迭代器模式的适用范围:
1) 访问一个容器对象的内容而无需暴露它的内部表示;
2) 支持对容器对象的多种遍历;
3) 为遍历不同的容器结构提供一个统一的接口(多态迭代)。
三、Iterator 迭代器与 Iterable 接口
1、Iterator 迭代器接口 : java.util 包
Java 提供一个专门的迭代器接口 Iterator,我们可以对某个容器实现该 Interface,来提供标准的 Java 迭代器。
用 Iterator 模式实现遍历集合
Iterator 模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
例如,如果没有使用 Iterator,遍历一个数组 的方法是使用索引:
for(int i=0; i<array.size(); i++) { ... get(i) ... }
而 遍历一个HashSet 又 必须使用 while 循环或 foreach,但不能使用for循环:
while((e=e.next())!=null) { ... e.data() ... }
对以上两种方法,客户端都必须事先知道集合的类型(内部结构),访问代码和集合本身是紧耦合的,无法将访问逻辑从集合类和客户端代码中分离出来,从而导致每一种集合对应一种遍历方法,客户端代码无法复用。更恐怖的是,如果以后需要把 ArrayList 更换为 LinkedList,则原来的客户端代码必须全部重写。
为解决以上问题,Iterator模式总是用同一种逻辑来遍历集合:
<p style="margin-bottom: 7px;">for(Iterator it = c.iterater(); it.hasNext(); ) { ... } <br/></p>
奥秘在于 客户端自身不维护遍历集合的”指针”,所有的内部状态(如当前元素位置,是否有下一个元素)都由 Iterator 来维护,而这个 Iterator 由集合类通过工厂方法生成,因此,它知道如何遍历整个集合。而且,客户端从不直接和集合类打交道,它总是控制Iterator,向它发送”向前”,”向后”,”取当前元素”的指令,就可以间接遍历整个集合。
首先看看 java.util.Iterator 接口的定义:
public interface Iterator { boolean hasNext(); Object next(); void remove(); // 可选操作 }
依赖前两个方法就能完成遍历,典型的代码如下:
for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 对o的操作... }
多态迭代 : 每一种集合类返回的 Iterator 具体类型可能不同,Array 可能返回 ArrayIterator,Set 可能返回 SetIterator,Tree 可能返回 TreeIterator,但是它们都实现了 Iterator 接口,因此,客户端不关心到底是哪种 Iterator,它只需要获得这个 Iterator 接口即可,这就是面向对象的威力。
2、Iterable 接口 : java.lang 包
Java 中还提供了一个 Iterable 接口,Iterable接口实现后的功能是“返回”一个迭代器 。我们常用的实现了该接口的子接口有: Collection
实现 Iterable 接口来实现适用于 foreach 遍历的自定义类
Iterable 接口包含一个能够产生 Iterator 的 iterator() 方法,并且 Iterable 接口被 foreach 用来在序列中实现移动。因此,实现这个接口允许对象成为 foreach 语句的目标,也就可以通过 foreach语法遍历你的底层序列。
在 JDK1.5 以前,用 Iterator 遍历序列的语法:
for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 对o的操作... }
在 JDK1.5 以及以后的版本中,引进了 foreach,对上面的代码在语法上作了简化 ( 但是限于只读,如果需要remove,还是直接使用 Iterator ):
for(Type t : collection) { ... }
3、思辨
为什么一定要去实现 Iterable 这个接口呢? 为什么不直接实现 Iterator接口 呢?
看一下 JDK 中的集合类,比如 List一族或者Set一族,都是实现了 Iterable 接口,但并不直接实现 Iterator 接口。仔细想一下这么做是有道理的:因为 Iterator接口的核心方法 next() 或者 hasNext() 是依赖于迭代器的当前迭代位置的。若 Collection 直接实现 Iterator 接口,势必导致集合对象中包含当前迭代位置的数据(指针)。当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么 next() 方法的结果会变成不可预知。除非再为 Iterator接口 添加一个 reset() 方法,用来重置当前迭代位置。但即使这样,Collection 也只能同时存在一个当前迭代位置(不能同时多次迭代同一个序列:必须要等到当前次迭代完成并reset后,才能再一次从头迭代)。 而选择实现 Iterable 接口则不然,每次调用都会返回一个从头开始计数的迭代器(Iterator),因此,多个迭代器间是互不干扰的。
四、foreach,Iterator,for
foreach 和 Iterator 的关系
foreach 是 jdk5.0 新增加的一个循环结构,可以用来处理集合中的每个元素而不用考虑集合的下标。
格式如下 :
for(variable:collection){ statement; }
定义一个变量用于暂存集合中的每一个元素,并执行相应的语句(块)。Collection 必须是一个数组或者是一个实现了 lterable 接口的类对象。
可以看出,使用 foreach 循环语句的优势在于更加简洁,更不容易出错,不必关心下标的起始值和终止值。forEach 不是关键字,关键字还是 for ,语句是由 iterator 实现的,它们最大的不同之处就在于 remove() 方法上。
特别地,一般调用删除和添加方法都是具体集合的方法,例如:
List list = new ArrayList(); list.add(...); list.remove(...); ...
但是,如果在循环的过程中调用集合的 remove() 方法,就会导致循环出错,因为循环过程中 list.size() 的大小变化了,就导致了错误(Iterator的快速失败机制
)。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器 iterator 的 remove() 方法,因为它的 remove() 方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次 next() 方法的调用。因此,foreach 就是为了让用 iterator 循环访问的形式简单,写起来更方便。当然功能不太全,所以若是需要使用删除操作,那么还是要用它原来的形式。
-
使用for循环与使用迭代器iterator的对比
从效率角度分析:
采用 ArrayList 对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快;
采用 LinkedList 则是顺序访问比较快,iterator 中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快。
从数据结构角度分析:
使用 for循环 适合访问有序结构,可以根据下标快速获取指定元素;而 Iterator 适合访问无序结构,因为迭代器是通过 next() 和 Pre() 来定位的,可以访问没有顺序的集合.
使用 Iterator 的好处在于可以使用相同方式去遍历集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样.
五、ListIterator 简述
1、简述
ListIterator 系列表迭代器,实现了Iterator
注意,remove() 和 set(Object) 方法不是根据光标位置定义的;它们是根据对调用 next() 或 previous() 所返回的最后一个元素的操作定义的。
2、与 Iterator 区别
Iterator 和 ListIterator 主要区别有:
ListIterator 有 add()方法,可以向 List 中添加对象,而 Iterator 不能 ;
ListIterator 和 Iterator 都有 hasNext()和next()方法,可以实现顺序向后遍历。但是 ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向(顺序向前)遍历,而 Iterator 就不可以 ;
ListIterator 可以利用 nextIndex() 和 previousIndex() 定位当前的索引位置,而 Iterator 没有此功能 ;
ListIterator 可以通过 listIterator() 方法和 listIterator(int index) 方法获得,而 Iterator 只能由 iterator() 方法获得 ;
二者都可以实现删除对象,但是ListIterator可以使用set()方法实现对象的修改。Iterator 仅能遍历,不能修改。因为ListIterator的这些功能,可以实现对LinkedList, ArrayList等List数据结构的操作。
以上是Java 迭代器的程式碼實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

Java是熱門程式語言,適合初學者和經驗豐富的開發者學習。本教學從基礎概念出發,逐步深入解說進階主題。安裝Java開發工具包後,可透過建立簡單的「Hello,World!」程式來實踐程式設計。理解程式碼後,使用命令提示字元編譯並執行程序,控制台上將輸出「Hello,World!」。學習Java開啟了程式設計之旅,隨著掌握程度加深,可創建更複雜的應用程式。
