Collection與Iterator的remove()方法區(qū)別與ConcurrentModificationException異常

在我的上一篇文章 Java中三種遍歷Collection中元素的方法(Iterator获茬、forEach牵咙、for循環(huán))對比 中提到Iterator和forEach循環(huán)在遍歷Collection中元素時最大的差別就是在方法remove()上,由于在Iterator的remove()方法中維護一個標志位,所以刪除元素時不會出現(xiàn)異常鳖昌,所以本篇文章就深入Collection與Iterator的源碼看看內(nèi)部究竟是如何實現(xiàn)的胖眷。

一. Collection及其實現(xiàn)類ArrayList的部分源碼

1.Collection內(nèi)部源碼

首先我們來看一下Collection內(nèi)部源碼(為方便分析恕酸,此處只展示與本篇文章有關(guān)的部分):

public interface Collection<E> extends Iterable<E> {

    boolean remove(Object o);

    Iterator<E> iterator();

    /**
     *  此處省去其他方法定義
     */
}

可以看到Collection是一個接口榕栏,內(nèi)部定義了remove()iterator()方法畔勤。

2.ArrayList內(nèi)部源碼

由于Collection接口內(nèi)部無具體實現(xiàn),所以我們來看Collection的一個最常用的實現(xiàn)類ArrayList內(nèi)部源碼(為方便分析扒磁,此處只展示與本篇文章有關(guān)的部分):

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }


    /* -----------------------我是便于觀察的分割線----------------------- */


    public Iterator<E> iterator() {
        return new Itr();
    }

    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();
        }

    /**
     *  此處省去其他方法定義
     */
}

在ArrayList中并沒有直接實現(xiàn)Collection接口庆揪,而是通過繼承AbstractList抽象類,而AbstractList抽象類又繼承了AbstractCollection抽象類妨托,最終AbstractCollection抽象類實現(xiàn)Collection接口缸榛;所以ArrayList間接實現(xiàn)了Collection接口,有興趣的大佬可以自己去研究下為什么這樣子設(shè)計兰伤,在這里就不多加討論内颗。

可以看到在ArrayList中有實現(xiàn)remove()iterator()方法,并且通過iterator()方法得到的內(nèi)部類Itr實現(xiàn)了Iterator接口敦腔,在Itr內(nèi)部類中也有實現(xiàn)remove()方法均澳,下面就來具體的探討其中的區(qū)別。

二. ArrayList的remove()方法分析

1.remove()方法

在ArrayList的remove()方法內(nèi)部的實現(xiàn)主要是通過循環(huán)找到元素的下標符衔, 然后調(diào)用私有的fastRemove()方法:

    fastRemove(index);

remove()方法沒啥好講的找前,關(guān)鍵在于調(diào)用的fastRemove()方法上。

2.fastRemove()方法

fastRemove()方法中會先修改modCount的值柏腻,然后將通過復制一個新的數(shù)組的方法將原來index位置上的值覆蓋掉纸厉,最后數(shù)組大小減一。我們重點關(guān)注fastRemove()方法的第一行代碼:

    modCount++;

也就是每次調(diào)用remove()方法都會使modCount的值加一五嫂。那么modCount變量又是什么呢?

3.modCount變量

modCount在ArrayList中沒有定義肯尺,是在ArrayList的父類AbstractList抽象類中定義的:

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

    protected transient int modCount = 0;

    /**
     *  此處省去其他方法定義
     */
}

modCount的作用是記錄操作(添加刪除)ArrayList中元素的次數(shù)(這個很關(guān)鍵)沃缘,每次操作ArrayList中元素后就會使modCount加一。

三. Iterator的remove()方法分析

看源碼可知ArrayList通過iterator()方法得到了一個內(nèi)部類Itr则吟,這個內(nèi)部類實現(xiàn)了Iterator接口槐臀,我們重點分析內(nèi)部類Itr中的實現(xiàn)。

1.expectedModCount 變量

在內(nèi)部類Itr中定義了一個變量expectedModCount :

    int expectedModCount = modCount;

expectedModCount 只在new一個Itr對象時初始化為modCount

2.next()與remove()方法

在調(diào)用Itr對象的next()remove()方法時第一步會先調(diào)用checkForComodification()方法氓仲。

    checkForComodification();

并且在remove()方法中會調(diào)用ArrayList.this.remove(lastRet)方法(也就是具體的ArrayList對象的remove()方法水慨,上面我們講過得糜,在ArrayList對象的remove()方法中會使得modCount的值加一),然后修改expectedModCount 的值為modCount晰洒。

3.checkForComodification()方法

checkForComodification()會檢查expectedModCount與modCount 是否相等朝抖,如果不相等就會拋出ConcurrentModificationException異常。

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

四. 總結(jié)

通過上面的分析我們可以得出谍珊,Collection與Iterator的remove()方法最大的區(qū)別就是:
Iterator的remove()方法會在刪除元素后將modCount 的值賦值給expectedModCount治宣,使其又相等

1.如果我們在Iterator循環(huán)中調(diào)用Collection的remove()方法

public static void display(Collection<Object> collection) {
    Iterator<Object> it = collection.iterator();

    // 會拋出ConcurrentModificationException異常
    while(it.hasNext()) {
        Object obj = it.next();
        collection.remove(obj ); 
    }
}

由于collection.remove(obj )只會刪除obj元素后將modCount 的值加一砌滞,并不會修改expectedModCount的值侮邀,所以當下一次調(diào)用it.next()方法時發(fā)現(xiàn)modCount != expectedModCount,將拋出ConcurrentModificationException異常贝润。

2.如果我們在Iterator循環(huán)中調(diào)用Iterator的remove()方法

public static void display(Collection<Object> collection) {
    Iterator<Object> it = collection.iterator();
    // 正常執(zhí)行
    while(it.hasNext()) {
        Object obj = it.next();
        it.remove(obj ); 
    }
}

由于it.remove(obj )會在刪除obj元素后將modCount 的值加一绊茧,并將expectedModCount重新賦值為modCount ,使其相等打掘,所以當下一次調(diào)用it.next()方法時發(fā)現(xiàn)modCount == expectedModCount华畏,正常執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胧卤,一起剝皮案震驚了整個濱河市唯绍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枝誊,老刑警劉巖况芒,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叶撒,居然都是意外死亡绝骚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門祠够,熙熙樓的掌柜王于貴愁眉苦臉地迎上來压汪,“玉大人,你說我怎么就攤上這事古瓤≈蛊剩” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵落君,是天一觀的道長穿香。 經(jīng)常有香客問我,道長绎速,這世上最難降的妖魔是什么皮获? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮纹冤,結(jié)果婚禮上洒宝,老公的妹妹穿的比我還像新娘购公。我一直安慰自己,他們只是感情好雁歌,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布宏浩。 她就那樣靜靜地躺著,像睡著了一般将宪。 火紅的嫁衣襯著肌膚如雪绘闷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天较坛,我揣著相機與錄音印蔗,去河邊找鬼。 笑死丑勤,一個胖子當著我的面吹牛华嘹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播法竞,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼耙厚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了岔霸?” 一聲冷哼從身側(cè)響起薛躬,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎呆细,沒想到半個月后型宝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡絮爷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年趴酣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坑夯。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡岖寞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柜蜈,到底是詐尸還是另有隱情仗谆,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布淑履,位于F島的核電站胸私,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鳖谈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一阔涉、第九天 我趴在偏房一處隱蔽的房頂上張望缆娃。 院中可真熱鬧捷绒,春花似錦、人聲如沸贯要。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崇渗。三九已至字逗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宅广,已是汗流浹背葫掉。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留跟狱,地道東北人俭厚。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像驶臊,于是被迫代替她去往敵國和親挪挤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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