java集合類(lèi)迭代器

迭代器模式:就是提供一種方法對(duì)一個(gè)容器對(duì)象中的各個(gè)元素進(jìn)行訪(fǎng)問(wèn)娱节,而又不暴露該對(duì)象容器的內(nèi)部細(xì)節(jié)。

概述

Java集合框架的集合類(lèi)辐益,我們有時(shí)候稱(chēng)之為容器断傲。容器的種類(lèi)有很多種,比如ArrayList智政、LinkedList艳悔、HashSet...,每種容器都有自己的特點(diǎn)女仰,ArrayList底層維護(hù)的是一個(gè)數(shù)組猜年;LinkedList是鏈表結(jié)構(gòu)的;HashSet依賴(lài)的是哈希表疾忍,每種容器都有自己特有的數(shù)據(jù)結(jié)構(gòu)乔外。

因?yàn)槿萜鞯膬?nèi)部結(jié)構(gòu)不同,很多時(shí)候可能不知道該怎樣去遍歷一個(gè)容器中的元素一罩。所以為了使對(duì)容器內(nèi)元素的操作更為簡(jiǎn)單杨幼,Java引入了迭代器模式!

把訪(fǎng)問(wèn)邏輯從不同類(lèi)型的集合類(lèi)中抽取出來(lái)聂渊,從而避免向外部暴露集合的內(nèi)部結(jié)構(gòu)差购。

對(duì)于數(shù)組的遍歷,使用下標(biāo)完成:

int array[] = new int[3];    
 for (int i = 0; i < array.length; i++) {
     System.out.println(array[i]);
}

對(duì)于ArrayList的遍歷汉嗽,同樣是使用序號(hào):

List<String> list = new ArrayList<String>();
        for(int i = 0 ; i < list.size() ;  i++){
           String string = list.get(i);
 }

對(duì)于這兩種方式欲逃,我們總是都知道它的內(nèi)部結(jié)構(gòu),訪(fǎng)問(wèn)代碼和集合本身是緊密耦合的饼暑,無(wú)法將訪(fǎng)問(wèn)邏輯從集合類(lèi)和客戶(hù)端代碼中分離出來(lái)稳析。不同的集合會(huì)對(duì)應(yīng)不同的遍歷方法,客戶(hù)端代碼無(wú)法復(fù)用弓叛。在實(shí)際應(yīng)用中如何將上面兩個(gè)集合整合是相當(dāng)麻煩的彰居。所以才有Iterator,它總是用同一種邏輯來(lái)遍歷集合撰筷。使得客戶(hù)端自身不需要來(lái)維護(hù)集合的內(nèi)部結(jié)構(gòu)陈惰,所有的內(nèi)部狀態(tài)都由Iterator來(lái)維護(hù)”献眩客戶(hù)端不用直接和集合進(jìn)行打交道抬闯,而是控制Iterator向它發(fā)送向前向后的指令,就可以遍歷集合影钉。

java.util.Interator

在Java中Iterator為一個(gè)接口画髓,它只提供了迭代的基本規(guī)則。在JDK中它是這樣定義的:對(duì)Collection進(jìn)行迭代的迭代器平委。迭代器取代了Java Collection Framework中的Enumeration奈虾。迭代器與枚舉有兩點(diǎn)不同:

  1. 迭代器在迭代期間可以從集合中移除元素。

  2. 方法名得到了改進(jìn)廉赔,Enumeration的方法名稱(chēng)都比較長(zhǎng)肉微。

其接口定義如下:

package java.util;
public interface Iterator<E> {
    boolean hasNext();//判斷是否存在下一個(gè)對(duì)象元素

    E next();//獲取下一個(gè)元素

    void remove();//移除元素
}

Interable

Java中還提供了一個(gè)Iterable接口,Iterable接口實(shí)現(xiàn)后的功能是‘返回’一個(gè)迭代器蜡塌,我們常用的實(shí)現(xiàn)了該接口的子接口有:Collection<E>碉纳、List<E>、Set<E>等馏艾。該接口的iterator()方法返回一個(gè)標(biāo)準(zhǔn)的Iterator實(shí)現(xiàn)劳曹。實(shí)現(xiàn)Iterable接口允許對(duì)象成為Foreach語(yǔ)句的目標(biāo)奴愉。就可以通過(guò)foreach語(yǔ)句來(lái)遍歷你的底層序列。

Iterable接口包含一個(gè)能產(chǎn)生Iterator對(duì)象的方法铁孵,并且Iterable被foreach用來(lái)在序列中移動(dòng)锭硼。因此如果創(chuàng)建了實(shí)現(xiàn)Iterable接口的類(lèi),都可以將它用于foreach中蜕劝。

interable接口的具體實(shí)現(xiàn):

Package java.lang;

import java.util.Iterator;
public interface Iterable<T> {
    Iterator<T> iterator();
}

使用迭代器遍歷集合:

public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("張三1");
        list.add("張三2");
        list.add("張三3");
        list.add("張三4");
        
        List<String> linkList = new LinkedList<String>();
        linkList.add("link1");
        linkList.add("link2");
        linkList.add("link3");
        linkList.add("link4");
        
        Set<String> set = new HashSet<String>();
        set.add("set1");
        set.add("set2");
        set.add("set3");
        set.add("set4");
        //使用迭代器遍歷ArrayList集合
        Iterator<String> listIt = list.iterator();
        while(listIt.hasNext()){
            System.out.println(listIt.hasNext());
        }
        //使用迭代器遍歷Set集合
        Iterator<String> setIt = set.iterator();
        while(setIt.hasNext()){
            System.out.println(listIt.hasNext());
        }
        //使用迭代器遍歷LinkedList集合
        Iterator<String> linkIt = linkList.iterator();
        while(linkIt.hasNext()){
            System.out.println(listIt.hasNext());
        }
}

使用foreach遍歷集合:

List<String> list = new ArrayList<String>();
        list.add("張三1");
        list.add("張三2");
        list.add("張三3");
        list.add("張三4");
        for (String string : list) {
            System.out.println(string);
        }

可以看出使用foreach遍歷集合的優(yōu)勢(shì)在于代碼更加的簡(jiǎn)潔檀头,更不容易出錯(cuò),不用關(guān)心下標(biāo)的起始值和終止值岖沛。

Iterator遍歷時(shí)不可以刪除集合中的元素問(wèn)題

在使用Iterator的時(shí)候禁止對(duì)所遍歷的容器進(jìn)行改變其大小結(jié)構(gòu)的操作暑始。例如: 在使用Iterator進(jìn)行迭代時(shí),如果對(duì)集合進(jìn)行了add婴削、remove操作就會(huì)出現(xiàn)==ConcurrentModificationException==異常廊镜。

List<String> list = new ArrayList<String>();
        list.add("張三1");
        list.add("張三2");
        list.add("張三3");
        list.add("張三4");
        
        //使用迭代器遍歷ArrayList集合
        Iterator<String> listIt = list.iterator();
        while(listIt.hasNext()){
            Object obj = listIt.next();
            if(obj.equals("張三3")){
                list.remove(obj);
            }
        }

因?yàn)樵谀愕埃饕呀?jīng)被通過(guò)list.itertor()創(chuàng)建出來(lái)了馆蠕,如果在迭代的過(guò)程中期升,又對(duì)list進(jìn)行了改變其容器大小的操作,那么Java就會(huì)給出異常互躬。因?yàn)榇藭r(shí)Iterator對(duì)象已經(jīng)無(wú)法主動(dòng)同步list做出的改變播赁,Java會(huì)認(rèn)為你做出這樣的操作是線(xiàn)程不安全的,就會(huì)給出善意的提醒(拋出ConcurrentModificationException異常)

Iterator的實(shí)現(xiàn)源碼:

private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

通過(guò)查看源碼發(fā)現(xiàn)原來(lái)檢查并拋出異常的是checkForComodification()方法吼渡。在ArrayList中==modCount是當(dāng)前集合的版本號(hào)==容为,每次修改(增、刪)集合都會(huì)加1寺酪;==expectedModCount是當(dāng)前迭代器的版本號(hào)==坎背,在迭代器實(shí)例化時(shí)初始化為modCount。我們看到在checkForComodification()方法中就是在驗(yàn)證modCount的值和expectedModCount的值是否相等寄雀,所以當(dāng)你在調(diào)用了ArrayList.add()或者ArrayList.remove()時(shí)得滤,只更新了modCount的狀態(tài),而迭代器中的expectedModCount未同步盒犹,因此才會(huì)導(dǎo)致再次調(diào)用Iterator.next()方法時(shí)拋出異常懂更。但是為什么使用Iterator.remove()就沒(méi)有問(wèn)題呢?通過(guò)源碼的第32行發(fā)現(xiàn)急膀,在Iterator的remove()中同步了expectedModCount的值沮协,所以當(dāng)你下次再調(diào)用next()的時(shí)候,檢查不會(huì)拋出異常卓嫂。

使用該機(jī)制的主要目的是為了實(shí)現(xiàn)ArrayList中的快速失敗機(jī)制(fail-fast)慷暂,在Java集合中較大一部分集合是存在快速失敗機(jī)制的。

==快速失敗機(jī)制產(chǎn)生的條件:當(dāng)多個(gè)線(xiàn)程對(duì)Collection進(jìn)行操作時(shí)晨雳,若其中某一個(gè)線(xiàn)程通過(guò)Iterator遍歷集合時(shí)行瑞,該集合的內(nèi)容被其他線(xiàn)程所改變奸腺,則會(huì)拋出ConcurrentModificationException異常。==

所以要保證在使用Iterator遍歷集合的時(shí)候不出錯(cuò)誤蘑辑,就應(yīng)該保證在遍歷集合的過(guò)程中不會(huì)對(duì)集合產(chǎn)生結(jié)構(gòu)上的修改洋机。

使用Foreach時(shí)對(duì)集合的結(jié)構(gòu)進(jìn)行修改會(huì)出現(xiàn)異常:

上面我們說(shuō)了實(shí)現(xiàn)了Iterable接口的類(lèi)就可以通過(guò)Foreach遍歷,那是因?yàn)閒oreach要依賴(lài)于Iterable接口返回的Iterator對(duì)象洋魂,所以從本質(zhì)上來(lái)講,F(xiàn)oreach其實(shí)就是在使用迭代器喜鼓,在使用foreach遍歷時(shí)對(duì)集合的結(jié)構(gòu)進(jìn)行修改副砍,和在使用Iterator遍歷時(shí)對(duì)集合結(jié)構(gòu)進(jìn)行修改本質(zhì)上是一樣的。所以同樣的也會(huì)拋出異常庄岖,執(zhí)行快速失敗機(jī)制豁翎。

foreach是JDK1.5新增加的一個(gè)循環(huán)結(jié)構(gòu),foreach的出現(xiàn)是為了簡(jiǎn)化我們遍歷集合的行為隅忿。

for循環(huán)與迭代器的對(duì)比:

  • 效率上各有各的優(yōu)勢(shì):

ArrayList對(duì)隨機(jī)訪(fǎng)問(wèn)比較快心剥,而for循環(huán)中使用的get()方法,采用的即是隨機(jī)訪(fǎng)問(wèn)的方法背桐,因此在ArrayList里for循環(huán)快优烧。

LinkedList則是順序訪(fǎng)問(wèn)比較快,Iterator中的next()方法采用的是順序訪(fǎng)問(wèn)方法链峭,因此在LinkedList里使用Iterator較快畦娄。

主要還是要依據(jù)集合的數(shù)據(jù)結(jié)構(gòu)不同的判斷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弊仪,一起剝皮案震驚了整個(gè)濱河市熙卡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌励饵,老刑警劉巖驳癌,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異役听,居然都是意外死亡颓鲜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)禾嫉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)灾杰,“玉大人,你說(shuō)我怎么就攤上這事熙参⊙薹停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵孽椰,是天一觀的道長(zhǎng)昭娩。 經(jīng)常有香客問(wèn)我凛篙,道長(zhǎng),這世上最難降的妖魔是什么栏渺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任呛梆,我火速辦了婚禮,結(jié)果婚禮上磕诊,老公的妹妹穿的比我還像新娘填物。我一直安慰自己,他們只是感情好霎终,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布滞磺。 她就那樣靜靜地躺著,像睡著了一般莱褒。 火紅的嫁衣襯著肌膚如雪击困。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天广凸,我揣著相機(jī)與錄音阅茶,去河邊找鬼。 笑死谅海,一個(gè)胖子當(dāng)著我的面吹牛脸哀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胁赢,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼企蹭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了智末?” 一聲冷哼從身側(cè)響起谅摄,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎系馆,沒(méi)想到半個(gè)月后送漠,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡由蘑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年闽寡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尼酿。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爷狈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出裳擎,到底是詐尸還是另有隱情涎永,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站羡微,受9級(jí)特大地震影響谷饿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜妈倔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一博投、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盯蝴,春花似錦毅哗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至松忍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間筷厘,已是汗流浹背鸣峭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酥艳,地道東北人摊溶。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像充石,于是被迫代替她去往敵國(guó)和親莫换。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容