ArrayList中remove方法的坑

問(wèn)題

ArrayList是我們經(jīng)常在代碼中使用的集合類(lèi),但是ArrayList在執(zhí)行remove方法時(shí)會(huì)出現(xiàn)ConcurrentModificationException。針對(duì)這個(gè)問(wèn)題進(jìn)行代碼分析。

例子

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
        
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("d");
    list.add("e");
    list.add("f");
    list.add("g");
    list.add("h");
        
    Iterator<String> it = list.iterator();
        
    while (it.hasNext()) {
        String str = it.next();
            
        if (str.equals("f")) {
            list.remove(str);
        }
    }
}

結(jié)果:


1.png

說(shuō)明:在Debug執(zhí)行過(guò)程中發(fā)現(xiàn),ArrayList在執(zhí)行remove方法移除元素之后,再執(zhí)行it.next這句話拋出了異常。

源碼分析

ArrayList的remove方法:

public E remove(int index) {
      // 先檢查下標(biāo)索引是是否越界
      rangeCheck(index);
      // ArrayList的修改次數(shù)加1
      modCount++;
      // 獲取索引對(duì)應(yīng)的元素值
      E oldValue = elementData(index);
      // 獲取刪除元素后啤月,需要移動(dòng)的元素的個(gè)數(shù)
      int numMoved = size - index - 1;
      if (numMoved > 0)
          // 將元素進(jìn)行移動(dòng)拷貝
          System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
      // 最后將多出的位置設(shè)置為空,這樣說(shuō)明是沒(méi)有引用的對(duì)象了
      elementData[--size] = null; // Let gc do its work
      // 返回刪除的舊值
      return oldValue;
}

說(shuō)明:思想是ArrayList的刪除方法劳跃,將指定位置元素刪除谎仲,然后將當(dāng)前位置后面的元素向前拷貝的方式移動(dòng)。但是注意一點(diǎn)細(xì)節(jié)刨仑,modCount++這步操作郑诺,將ArrayList的修改次數(shù)加1。而后面遍歷時(shí)發(fā)現(xiàn)是通過(guò)使用這個(gè)字段來(lái)判斷贸人,當(dāng)前的集合類(lèi)是否被并發(fā)修改间景。

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

說(shuō)明:
① 在初始化Itr時(shí)expectedModCount = modCount = 8 。
② 在執(zhí)行next方法的第一步先進(jìn)行了checkForComodification方法的檢查艺智,因?yàn)槲覀冎斑M(jìn)行了remove操作倘要,那么modCount數(shù)值減一,實(shí)際modCount = 7 十拣。
③ modCount 數(shù)值和expectedModCount 數(shù)值不相等封拧,拋出ConcurrentModificationException異常。

正確寫(xiě)法

使用Itr自身提供的remove方法:

public class MyTest {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");
        list.add("g");
        list.add("h");
        
        Iterator<String> it = list.iterator();
        
        while (it.hasNext()) {
            String str = it.next();
            
            if (str.equals("f")) {
                it.remove();
            }
        }
        
        System.out.println(list);
    }
}

結(jié)果:


2.png

說(shuō)明:使用Itr提供的remove方法夭问,可以發(fā)現(xiàn)expectedModCount是隨著modCount變化而發(fā)生變化的泽西,所以是不需要考慮modCount修改次數(shù)不一致的問(wèn)題。

思考

foreach中的remove方法實(shí)際上使用list.remove一樣會(huì)報(bào)ConcurrentModificationException異常缰趋。因?yàn)閒oreach在jvm中還是會(huì)解析成Iterator來(lái)執(zhí)行的捧杉,實(shí)際上和錯(cuò)誤例子是一樣的效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秘血,一起剝皮案震驚了整個(gè)濱河市味抖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灰粮,老刑警劉巖仔涩,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異粘舟,居然都是意外死亡熔脂,警方通過(guò)查閱死者的電腦和手機(jī)佩研,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)霞揉,“玉大人旬薯,你說(shuō)我怎么就攤上這事∈手龋” “怎么了袍暴?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)隶症。 經(jīng)常有香客問(wèn)我,道長(zhǎng)岗宣,這世上最難降的妖魔是什么蚂会? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮耗式,結(jié)果婚禮上胁住,老公的妹妹穿的比我還像新娘。我一直安慰自己刊咳,他們只是感情好彪见,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著娱挨,像睡著了一般余指。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上跷坝,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天酵镜,我揣著相機(jī)與錄音,去河邊找鬼柴钻。 笑死淮韭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贴届。 我是一名探鬼主播靠粪,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼毫蚓!你這毒婦竟也來(lái)了占键?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绍些,失蹤者是張志新(化名)和其女友劉穎捞慌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體柬批,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啸澡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年袖订,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗅虏。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洛姑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出皮服,到底是詐尸還是另有隱情楞艾,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布龄广,位于F島的核電站硫眯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏择同。R本人自食惡果不足惜两入,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敲才。 院中可真熱鬧裹纳,春花似錦、人聲如沸紧武。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)朋鞍。三九已至,卻和暖如春妥箕,著一層夾襖步出監(jiān)牢的瞬間番舆,已是汗流浹背恨狈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吗氏,地道東北人弦讽。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親往产。 傳聞我的和親對(duì)象是個(gè)殘疾皇子被碗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

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