執行ArrayList的remove(object)方法拋異常?

簡介

或許有很多小夥伴都嘗試過如下的代碼:

ArrayList<Object> list = ...;
for (Object object : list) {
    if (條件成立) {
        list.remove(object);
    }
}

然後會發現拋出java.util.ConcurrentModificationException異常,這是一個並發異常。那麼這個到底是什麼情況?首先需要介紹一下增強for循環

增強for循環

增強for循環是Java1.5後,Collection實現了Iterator接口後出現的。增強for循環的代碼如下

for (Object object : list) {
    // 操作
}

其實增強for循環就是使用Iterator迭代器進行迭代的,增強for循環就變成下面這樣:

Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()) {
    iterator.next();
    // 操作
}

那麼為什麼在增強for循環中調用list.remove(object)會出事呢?那麼咱們看看ArrayList下的 Iterator的實現類: Itr類

Itr子類

Itr子類是Iterator的實現類,屬於ArrayList私有的局部內部類。我截取了Itr類的部分代碼,如下:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int expectedModCount = modCount;

    Itr() {}

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

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        ...
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

elementData 是ArrayList存放元素的數組,上面代碼沒有貼出來。
size 是elementData實際存放的容量大小
modCount 記錄elementData容量的修改次數
expectedModCount 記錄實例化迭代器Itr時,elementData容量的修改次數
注意!:在迭代器中,當執行next方法的時候,會去調用checkForComodification方法,判斷elementData 的容量是否被修改過。

然後來看看ArrayList的remove(object)方法,截取部分代碼如下:

public boolean remove(Object o) {
    for (int index = 0; index < size; index++)
	    if (找到目標元素) {
	        fastRemove(index);
	        return true;
	    }
	return false;
}

private void fastRemove(int index) {
    modCount++;
    // 移除操作
}

可以發現,調用remove(object)方法時調用了fastRemove方法,在fastRemove方法中執行modCount++
現在把文章開頭的代碼拷下來,再來分析一次:

ArrayList<Object> list = ...;
for (Object object : list) {
    if (條件成立) {
        list.remove(object);
    }
}

當執行了list.remove時,執行modCount++ 。此時迭代器再往下進行迭代,執行了next方法,發現 modCount != expectedModCount,那麼則拋出java.util.ConcurrentModificationException異常。 之所以Iterator認為是一個並發異常。是因為你不在迭代器里操作,而是在迭代器外面進行remove操作呀!
難道沒有其他解決方案嗎?有滴。

解決方案

那麼就是使用Itr的 remove方法。Itr子類重寫了 remove 方法,這裡部分代碼:

public void remove() {
    ...
    try {
        ArrayList.this.remove(需要刪除元素的索引);
        ...
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

其實很簡單,就是remove後,把 expectedModCount 同步一下 modCount 的值,這就解決了。完整代碼如下:

ArrayList<Object> list = ...;
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()) {
    iterator.next();
    iterator.remove();
}

總結

本來我還不知道增強for循環是調用Iterator進行迭代的,要不是我debug了一波,我還不知道吶。還是小有收貨。

個人博客網址: //colablog.cn/

如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您
微信公眾號

Tags: