Iterable和Iterator之間的區(qū)別以及延伸討論

本文會(huì)討論幾個(gè)問題

  • Iterable和Iterator的區(qū)別
  • 討論為什么需要Iterable和Iterator拣度,只保留Iterator行不行
  • 迭代器遍歷時(shí)萍桌,拋出ConcurrentModificationException的原因
  • foreach語法糖驗(yàn)證

首先看一下

Iterable的定義

public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

首先垫卤,Iterable是一個(gè)接口容客,由具體的類去實(shí)現(xiàn)冀泻,其中最重要的是這個(gè)方法桃序,

Iterator<T> iterator();

下面兩個(gè)方法是JDK8引入的新特性,其中forEach用于集合的遍歷溯职,另外一個(gè)不是很懂精盅。

我們看看是有哪些接口和類繼承或?qū)崿F(xiàn)了Iterable

image-20190104101610515.png

我們常見的ListSet缸榄,Queue接口都是間接繼承了Iterable接口渤弛,并且由抽象類AbstractList祝拯、AbstractSet甚带、AbstractQueue實(shí)現(xiàn)了Iterable接口,也就是這幾個(gè)抽象類必須去實(shí)現(xiàn)iterator()方法佳头。

Tips:在看類的繼承結(jié)構(gòu)中鹰贵,IDEA中可以設(shè)置范圍,過濾一些雜亂的包康嘉。

在看看Iterator接口

public interface Iterator<E> {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

這個(gè)接口有幾個(gè)方法碉输,hasNext()是否有下一個(gè)元素,next()獲取下一個(gè)元素亭珍,remove()移除元素敷钾。

說到IterableIterator區(qū)別,從接口的定義來說肄梨,他們是兩個(gè)完全不一樣的接口阻荒;從它們的名字來說,Iterable是表示可迭代的众羡,Iterator是表示一個(gè)迭代器侨赡。他們的聯(lián)系在于Iterable接口可以通過iterator()方法拿到Iterator,在JDK8之前粱侣,

Iterable也僅僅只有這個(gè)方法羊壹,也就是說Iterable的作用就是為了拿到Iterator

接下來思考一個(gè)問題齐婴,為什么集合類不直接去實(shí)現(xiàn)Iterator接口油猫,而是通過實(shí)現(xiàn)Iterable,再去拿到Iterator呢柠偶?

Iterator迭代器的作用就是為了順序地遍歷元素眨攘,為了拿到下一個(gè)元素和判斷是否有下一個(gè)元素主慰,Iterator的實(shí)現(xiàn)類必須要有一個(gè)游標(biāo)變量來記錄當(dāng)前的位置。

也就是說鲫售,如果集合類直接去實(shí)現(xiàn)Iterator接口共螺,那它也必須要有一個(gè)游標(biāo)變量來記錄,那這樣子直接實(shí)現(xiàn)可不可以呢情竹?當(dāng)然也是可以的藐不,但是會(huì)存在一些問題。首先秦效,你的游標(biāo)變量在第一次迭代之后雏蛮,什么時(shí)候應(yīng)該去重置呢?這要求Iterator接口提供一個(gè)重置reset()的方法阱州。另外一個(gè)問題挑秉,游標(biāo)變量只有一個(gè),如果我們對(duì)這個(gè)集合類同時(shí)進(jìn)行迭代呢苔货?這樣子游標(biāo)變量整個(gè)就亂套了犀概。

因此,如果Iterator接口由專門的子類去實(shí)現(xiàn)夜惭,由子類自己去維護(hù)游標(biāo)變量姻灶,每次拿到迭代器,每個(gè)迭代器的游標(biāo)變量是不會(huì)相互影響的诈茧,這就避免了上面說到的問題产喉。

下面看一下,ArrayListItr實(shí)現(xiàn)了Iterator接口

private class Itr implements Iterator<E> {
    // 游標(biāo)變量敢会,為了下一個(gè)元素
    int cursor;       // index of next element to return
    // 上個(gè)元素返回的下標(biāo)
    int lastRet = -1; // index of last element returned; -1 if no such
    // expectedModCount期望曾沈,檢查迭代器遍歷時(shí)List是否被修改
    int expectedModCount = modCount;

    Itr() {}

    //判斷是否有下一個(gè)元素
    public boolean hasNext() {
        return cursor != size;
    }

    //拿到下一個(gè)元素
    @SuppressWarnings("unchecked")
    public E next() {
        //檢查L(zhǎng)ist是否被修改過
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        // 游標(biāo)進(jìn)行加1
        cursor = i + 1;
        // 返回元素
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        //lastRet每次remove()都會(huì)被賦值為-1
        if (lastRet < 0)
            throw new IllegalStateException();
        //檢查L(zhǎng)ist是否被修改過
        checkForComodification();

        try {
            //移除上次返回的元素
            ArrayList.this.remove(lastRet);
            //因?yàn)橐瞥氐臅r(shí)候,下標(biāo)會(huì)往前移動(dòng)1位
            //所以相應(yīng)地cursor也要減1鸥昏,也就是lastRet返回的位置
            cursor = lastRet;
            //賦值為-1
            lastRet = -1;
            //給expectedModCount重新賦值塞俱,要不會(huì)引起ConcurrentModificationException
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

在上面的Itr類中,維護(hù)了3個(gè)變量互广,一個(gè)是游標(biāo)變量cursor敛腌,一個(gè)是上次元素返回的下標(biāo)lastRet,以及防止迭代器遍歷中List被修改的變量expectedModCount惫皱。

cursor保證了拿到下一個(gè)元素以及判斷是否有下一個(gè)元素像樊,lastRet用于刪除元素,expectedModCount則是檢查L(zhǎng)ist是否被修改過旅敷。

Note:不要在遍歷迭代器的時(shí)候去修改List生棍,通過List的remove()也好,add()也好媳谁,都會(huì)引起modCount的改變涂滴,從而會(huì)導(dǎo)致拋出ConcurrentModificationException異常友酱。

例子

    @Test
    public void testForEach() {
        
        // 初始化List
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        
        //遍歷
        for (Integer integer : list) {
            System.out.println(integer);
            if ( integer == 1) {
                list.remove(integer); //對(duì)List進(jìn)行修改,modCount會(huì)加1柔纵,會(huì)導(dǎo)致expectedModCount != modCount會(huì)加1,下次調(diào)用next()時(shí)會(huì)拋出ConcurrentModificationException
            }
        }
        
        //這段跟上面的一樣缔杉,foreach只是語法糖,編譯后還是使用迭代器進(jìn)行遍歷的
        Iterator<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            Integer next = iterator.next();
            System.out.println(next);
            if (next == 1) {
                list.remove(next);
            }
        }
        
        // 這個(gè)才是正確移除元素的做法
        // 使用迭代器的remove()方法搁料,而不是調(diào)用List的remove()方法
        Iterator<Integer> iterator1 = list.iterator();
        while (iterator1.hasNext()) {
            Integer next = iterator1.next();
            System.out.println(next);
            iterator1.remove();
        }

foreach語法糖驗(yàn)證

原始的.java文件

public class ForEachGrammerTest {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        for (Integer integer : list) {
            System.out.println(integer);
        }
    }
}

編譯后的.class文件

public class ForEachGrammerTest {
    public ForEachGrammerTest() {
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            Integer integer = (Integer)var2.next();
            System.out.println(integer);
        }

    }
}

從上面兩個(gè)文件中可以看出來或详,foreach語法也是要變成迭代器去遍歷的呀。

以上內(nèi)容郭计,個(gè)人理解霸琴,如有錯(cuò)誤,請(qǐng)指正昭伸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梧乘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子庐杨,更是在濱河造成了極大的恐慌选调,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辑莫,死亡現(xiàn)場(chǎng)離奇詭異学歧,居然都是意外死亡罩引,警方通過查閱死者的電腦和手機(jī)各吨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袁铐,“玉大人揭蜒,你說我怎么就攤上這事√藿埃” “怎么了屉更?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)洒缀。 經(jīng)常有香客問我瑰谜,道長(zhǎng),這世上最難降的妖魔是什么树绩? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任萨脑,我火速辦了婚禮,結(jié)果婚禮上饺饭,老公的妹妹穿的比我還像新娘渤早。我一直安慰自己,他們只是感情好瘫俊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布鹊杖。 她就那樣靜靜地躺著悴灵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骂蓖。 梳的紋絲不亂的頭發(fā)上积瞒,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音登下,去河邊找鬼赡鲜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛庐船,可吹牛的內(nèi)容都是我干的银酬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼筐钟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼揩瞪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起篓冲,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤李破,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后壹将,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗤攻,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年诽俯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妇菱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暴区,死狀恐怖闯团,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仙粱,我是刑警寧澤房交,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站伐割,受9級(jí)特大地震影響候味,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隔心,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一白群、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧济炎,春花似錦川抡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侍咱。三九已至,卻和暖如春密幔,著一層夾襖步出監(jiān)牢的瞬間楔脯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工胯甩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昧廷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓偎箫,卻偏偏與公主長(zhǎng)得像木柬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淹办,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • 1 場(chǎng)景問題# 1.1 工資表數(shù)據(jù)的整合## 考慮這樣一個(gè)實(shí)際應(yīng)用:整合工資表數(shù)據(jù)眉枕。 這個(gè)項(xiàng)目的背景是這樣的,項(xiàng)目...
    七寸知架構(gòu)閱讀 2,550評(píng)論 0 53
  • Iterator(遍歷器)的概念 JavaScript原有的表示“集合”的數(shù)據(jù)結(jié)構(gòu)怜森,主要是數(shù)組和對(duì)象速挑,ES6又添加...
    oWSQo閱讀 614評(píng)論 0 1
  • 農(nóng)歷十一月十七 星期日 雨 親愛的小兜兜: 今天是你出生第七十八天,現(xiàn)在你正躺在媽媽懷里喝奶副硅。 昨天睡眠...
    柚西的小世界閱讀 347評(píng)論 2 5
  • 每當(dāng)我想起那一年恐疲,羞于表達(dá)的我們腊满。 我不知道應(yīng)該是感動(dòng)還是感激。 因?yàn)榱魑疲龅侥銈兠优耄庞鞋F(xiàn)在我的和我所經(jīng)歷的一切违诗。 ...
    北方以南閱讀 208評(píng)論 0 1
  • 人生故有過錯(cuò)漱凝,亦然承受其果。做事應(yīng)謀定而后動(dòng)诸迟,不謀而做是為莽茸炒,謀而不做則殆。然事有利弊阵苇,不可單受其利而避其弊壁公,謀...
    攜清風(fēng)閱讀 268評(píng)論 1 2