設(shè)計(jì)模式系列之迭代器模式
Iterator大家應(yīng)該都很熟悉了,作為Java程序員的我們來說,遍歷集合這也是我們剛開始學(xué)習(xí)Java知識(shí)。
遍歷集合的方式也有很多,比如for循環(huán)、while循環(huán)、foreach循環(huán)、Iterator等。這里的Iterator就是我們?cè)O(shè)計(jì)模式里面的迭代器模式。
這次要跟大家分享的設(shè)計(jì)模式就是這迭代器模式,雖然很多語言都直接把Iterator封裝到基礎(chǔ)工具類中,但是它的特性你都了解嗎?
本期大綱
定義
迭代器大家都很熟悉,那么什么叫迭代器?它的目的又是什么呢?
- 定義:我們可以用相同的方式處理集合,無論它是列表還是數(shù)組,它都提供了一種迭代其元素而不用暴露其內(nèi)部結(jié)構(gòu)的機(jī)制,更重要的是,不同的類型的集合都可以使用相同的統(tǒng)一機(jī)制,這種機(jī)制則被稱為 迭代器模式。
- 目的:提供一種順序遍歷聚合對(duì)象元素,而不暴露其內(nèi)部實(shí)現(xiàn)的方法。
以上定義來之設(shè)計(jì)模式之美
解析圖:
- Aggregate(抽象容器):負(fù)責(zé)提供創(chuàng)建具體迭代器角色的接口,對(duì)應(yīng)于java.util.Collection接口。
- Iterator(抽象迭代器):迭代器的抽象類,它定義遍歷容器對(duì)象的操作以及返回對(duì)象的操作
- ConcreteAggregate(具體容器):主要是可以實(shí)現(xiàn)內(nèi)部不同的結(jié)構(gòu)。但會(huì)暴露處理遍歷容器的具體迭代器。
- ConcreteIterator(具體迭代器):處理特定的具體容器類的具體迭代器,實(shí)際上對(duì)于每個(gè)容器具體容器,都必須實(shí)現(xiàn)一個(gè)具體的迭代器。
整個(gè)圖看起來其實(shí)就兩個(gè)東西,一個(gè)容器,一個(gè)迭代器。
代碼實(shí)現(xiàn)
這次就不舉列了。直接手寫一個(gè)迭代器,我們?cè)贉y試一下。
主要還是理解迭代器到底是干嘛用的:
- 能在不暴露集合底層表現(xiàn)形式 (列表、 棧和樹等) 的情況下遍歷集合中所有的元素
話不多說,還是直接上手?jǐn)]代碼
- public interface Aggregate {
- // 添加元素
- void add(Object object);
- // 移除元素
- void remove(Object object);
- // 迭代器
- Iterator iterator();
- }
按照上面的類圖,先是創(chuàng)建抽象容器,定義幾個(gè)基本添加刪除元素方法,以及迭代器
- public interface Iterator<E> {
- // 判斷容器是否有值
- boolean hasNext();
- // 把游標(biāo)執(zhí)向下一個(gè)指針
- void next();
- // 當(dāng)前遍歷的數(shù)據(jù)
- E currentItem();
- }
其次 再試創(chuàng)建抽象迭代器,遍歷容器中的數(shù)據(jù)
- public class ConcreteAggregate implements Aggregate {
- private ArrayList arrayList = new ArrayList();
- @Override
- public void add(Object object) {
- this.arrayList.add(object);
- }
- @Override
- public void remove(Object object) {
- this.arrayList.remove(object);
- }
- @Override
- public Iterator iterator() {
- return new ConcreteIterator(this.arrayList);
- }
- }
開始定義我們具體的容器了,內(nèi)部定一個(gè)ArrayList容器,用來存放數(shù)據(jù),當(dāng)然這里大家也可以改成其他的容器 比如說用Vector 或者其他的 棧、 樹、 圖 等
- public class ConcreteIterator<E> implements Iterator<E> {
- private int cursor; // 游標(biāo)
- private ArrayList arrayList;
- public ConcreteIterator(ArrayList arrayList) {
- this.cursor = 0;
- this.arrayList = arrayList;
- }
- @Override
- public boolean hasNext() {
- if (this.cursor == this.arrayList.size()) {
- return false;
- }
- return true;
- }
- @Override
- public void next() {
- cursor++;
- System.out.println(cursor + " cursor");
- }
- @Override
- public E currentItem() {
- if (cursor >= arrayList.size()) {
- throw new NoSuchElementException();
- }
- E e = (E) arrayList.get(cursor);
- this.next();
- return e;
- }
- // 測試demo
- public static void main(String[] args) {
- Aggregate aggregate = new ConcreteAggregate();
- aggregate.add("java");
- aggregate.add("c++");
- aggregate.add("php");
- aggregate.add("敖丙");
- Iterator iterator = aggregate.iterator();
- while (iterator.hasNext()) {
- System.out.println(iterator.currentItem());
- }
- // 結(jié)果:1 java
- // 2 c++
- // 3 php
- // 4 敖丙
- }
- }
最后就是實(shí)現(xiàn)具體的迭代器了, 在currentItem里面根據(jù)遍歷的游標(biāo),獲取數(shù)組里面的值
同時(shí)在main方法里面就是測試demo了,以上就是簡單的手?jǐn)]迭代器了。
這里面我們其實(shí)還可以有其它的各種特別的玩法,比如說怎么實(shí)現(xiàn)暫停遍歷等,只有了解內(nèi)部實(shí)現(xiàn),我們才能改造出符合當(dāng)前所需要的業(yè)務(wù)代碼。
Java中的迭代器
在Java的中也有迭代器,java.util.Iterator類以及java.util.Collection,就是典型的迭代器喝容器的列子,接下來看看具體的源碼
當(dāng)next沒有值的時(shí)候則會(huì)拋出NoSuchElementException異常信息,上面的手?jǐn)]異常也是根據(jù)這個(gè)來的
在Java中 常見的 List、Set、Queue都是extend Collection(容器),而Collection又定義迭代器Iterator,這就是能直接使用的原因了。
Java集合分析
上面我們看完了Java中的迭代器,不知道,大家注意了沒有,我們?cè)谑褂玫鞯臅r(shí)候是不能再對(duì)集合進(jìn)行增減操作的,否則就會(huì)拋出ConcurrentModificationException異常
那么問題來了,為什么會(huì)有這個(gè)異常信息呢?
看過ArrayList源碼的同學(xué)都知道底層是數(shù)據(jù)結(jié)構(gòu)中的數(shù)組結(jié)構(gòu)的,所以我們看下接下來圖結(jié)構(gòu)
假設(shè)現(xiàn)在開始在遍歷當(dāng)前這個(gè)數(shù)組,當(dāng)從第一步執(zhí)行到第二步,都是正常運(yùn)行的,假設(shè)現(xiàn)在執(zhí)行完第二步,開始走第三步時(shí)
刪除 java這個(gè)元素,數(shù)組為了保持存儲(chǔ)數(shù)據(jù)的連續(xù)性,當(dāng)刪除java數(shù)據(jù)時(shí),是會(huì)發(fā)生數(shù)組元素的遷移的。所以正常步驟3應(yīng)該是遍歷到aobing元素的變成當(dāng)前數(shù)組元素已經(jīng)是ao bing了。
導(dǎo)致了數(shù)組會(huì)發(fā)生ao bing沒有遍歷到,因?yàn)閿?shù)據(jù)遷移而丟失了。
同樣的假設(shè)在后面添加元素按照向后遷移,還能遍歷到,那如過插入的數(shù)據(jù)是在已經(jīng)遍歷的之前呢?
這樣整個(gè)遍歷就變成不可預(yù)估了。
- public static void main(String[] args) {
- List<String> aggregate = new ArrayList();
- aggregate.add("java");
- aggregate.add("c++");
- aggregate.add("php");
- aggregate.add("敖丙");
- Iterator<String> iterator = aggregate.iterator();
- while (iterator.hasNext()) {
- iterator.remove(); // 添加這行代碼 java.lang.IllegalStateException
- System.out.println(iterator.next());
- iterator.remove(); // 正常
- }
- }
再來看這個(gè)測試demo,同樣都是調(diào)用remove方法,不同的地方結(jié)果不一樣,這也就是剛好印證上面的圖體現(xiàn)的問題,所以要解決這個(gè)問題,要么就是遍歷的時(shí)候不允許增刪元素,要么是增刪元素之后讓遍歷報(bào)錯(cuò)。
通過上面的列子已經(jīng)了解了迭代器的原理以及實(shí)現(xiàn),大家可以根據(jù)自己所需要的場景改造迭代器,很多公司的一些自己的框架或者工具類等等都是通過現(xiàn)有框架源碼進(jìn)行改造而來。
迭代器的優(yōu)點(diǎn):
- 迭代器模式封裝集合內(nèi)部的復(fù)雜數(shù)據(jù)結(jié)構(gòu),不用關(guān)心需要遍歷的對(duì)象。
- 符合單一職責(zé)原則以及開閉原則
- 可以對(duì)遍歷進(jìn)行把控暫?;蛘呃^續(xù)
總結(jié)
迭代器設(shè)計(jì)模式在我們業(yè)務(wù)場景中自己寫的代碼中 我個(gè)人是覺得比較少見的,至少到目前我還沒有怎么發(fā)現(xiàn)有好的業(yè)務(wù)場景可以用這個(gè)模式,所以這里我就不給大家舉例業(yè)務(wù)代碼改造了。(畢竟不能因?yàn)樵O(shè)計(jì)模式而強(qiáng)行設(shè)計(jì))
跟大家分享迭代器主要是想讓大家了解Java集合遍歷怎么實(shí)現(xiàn)的,方便我們提升自己以后的看源碼的能力,以及提升自己的設(shè)計(jì)能力。
后面就再跟大家再聊聊動(dòng)態(tài)代理設(shè)計(jì)模式,就不會(huì)再詳細(xì)講了其他的模式了,因?yàn)楸旧聿辉趺闯R姡鳛榱私膺€是會(huì)和大家做一個(gè)總結(jié)分享。
今天的迭代器模式到此結(jié)束,我是敖丙,你知道的越多,你不知道的越多,我們下期見!!!