foreach遍歷list刪除元素一定會(huì)報(bào)錯(cuò)?

foreach遍歷list集合刪除某些元素一定會(huì)報(bào)錯(cuò)嗎?
先上一段代碼:

List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
for (String item : list) {
    if (item.equals("3")) {
        System.out.println(item);
        list.remove(item);
    }
}
System.out.println(list.size());

控制臺(tái)報(bào)錯(cuò):java.util.ConcurrentModificationException。
這是怎么回事,然后去看了看這個(gè)異常,才發(fā)現(xiàn)自己果然還是太年輕啊。
我們都知道增加for循環(huán)即foreach循環(huán)其實(shí)就是根據(jù)list對(duì)象創(chuàng)建一個(gè)iterator迭代對(duì)象,用這個(gè)迭代對(duì)象來(lái)遍歷list,相當(dāng)于list對(duì)象中元素的遍歷托管給了iterator,如果要對(duì)list進(jìn)行增刪操作,都必須經(jīng)過(guò)iterator。

每次foreach循環(huán)時(shí)都有以下兩個(gè)操作:

  1. iterator.hasNext(); //判讀是否有下個(gè)元素
  2. item = iterator.next(); //下個(gè)元素是什么,并把它賦給item。

首先,我們來(lái)看看這個(gè)異常信息是什么。

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

@SuppressWarnings("unchecked")
public E next() {
    checkForComodification(); // 此處報(bào)錯(cuò)
    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];
}

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

可以看到是進(jìn)入checkForComodification()方法的時(shí)候報(bào)錯(cuò)了,也就是說(shuō)modCount != expectedModCount。具體的原因是:以foreach方式遍歷元素的時(shí)候,會(huì)生成iterator,然后使用iterator遍歷。在生成iterator的時(shí)候,會(huì)保存一個(gè)expectedModCount參數(shù),這個(gè)是生成iterator的時(shí)候List中修改元素的次數(shù)。如果你在遍歷過(guò)程中刪除元素,List中modCount就會(huì)變化,如果這個(gè)modCount和exceptedModCount不一致,就會(huì)拋出異常,這個(gè)是為了安全考慮。

看看list的remove源碼:

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

看,并沒(méi)有對(duì)expectedModCount進(jìn)行任何修改,導(dǎo)致expectedModCount和modCount不一致,拋出異常。

但是,遍歷list刪除元素使用Iterator則不會(huì)報(bào)錯(cuò),如下:

Iterator it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("3")) {
        it.remove();
    }
}

看看Iterator的remove()方法的源碼,是對(duì)expectedModCount重新做了賦值處理的,如下:

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();
    
    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount; // 處理expectedModCount
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

這樣的話保持expectedModCount = modCount相等,就不會(huì)報(bào)出錯(cuò)了。

是不是foreach所有的list刪除操作都會(huì)報(bào)出這個(gè)錯(cuò)呢?

其實(shí)不一定。如果刪除的元素是倒數(shù)第二個(gè)數(shù)的話,其實(shí)是不會(huì)報(bào)錯(cuò)的。為什么呢,來(lái)一起看看。
之前說(shuō)了foreach循環(huán)會(huì)走兩個(gè)方法hasNext() 和next()。如果不想報(bào)錯(cuò)的話,只要不進(jìn)next()方法就好啦,看看hasNext()的方法。

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

那么就要求hasNext()的方法返回false了,即cursor == size。其中cursor是Itr類(Iterator子類)中的一個(gè)字段,用來(lái)保存當(dāng)前iterator的位置信息,從0開(kāi)始。cursor本身就是游標(biāo)的意思,在數(shù)據(jù)庫(kù)的操作中用的比較多。只要curosr不等于size就認(rèn)為存在元素。由于Itr是ArrayList的內(nèi)部類,因此直接調(diào)用了ArrayList的size字段,所以這個(gè)字段的值是動(dòng)態(tài)變化的,既然是動(dòng)態(tài)變化的可能就會(huì)有問(wèn)題出現(xiàn)了。
我們以上面的代碼為例,當(dāng)?shù)降箶?shù)第二個(gè)數(shù)據(jù)也就是“4”的時(shí)候,cursor是4,然后調(diào)用刪除操作,此時(shí)size由5變成了4,當(dāng)再調(diào)用hasNext判斷的時(shí)候,cursor==size,就會(huì)調(diào)用后面的操作直接退出循環(huán)了。我們可以在上面的代碼添加一行代碼查看效果:

for (String item : list) {
    System.out.println(item);
    if (item.equals("4")) {
        list.remove(item);
    }
}

輸出是:

1
2
3
4

這樣的話就可以看到執(zhí)行到hasNext()方法就退出了,也就不會(huì)走后面的異常了。
由此可以得出,用foreach刪除list元素的時(shí)候只有倒數(shù)第二個(gè)元素刪除不會(huì)報(bào)錯(cuò),其他都會(huì)報(bào)錯(cuò),所以刪除list元素時(shí)一定要用Iterator

參考文獻(xiàn):
https://blog.csdn.net/bimuyulaila/article/details/52088124

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容