1. 程式人生 > >ArrayList刪除倒數第二個元素不報ConcurrentModificationException原因-----阿里開發手冊

ArrayList刪除倒數第二個元素不報ConcurrentModificationException原因-----阿里開發手冊

最近看阿里的開發手冊發現當迭代ArrayList時刪除ArrayList的倒數第二個元素是不會報ConcurrentModificationException異常,為此個人寫了一下測試程式碼去ArrayList原始碼查找了一下原因,在說明前個人覺得還是需要先介紹一下List的foreach過程。

Java在通過foreach遍歷集合列表時,會先為列表建立對應的迭代器,並通過呼叫迭代器的hasNext()函式判斷是否含下一個元素,若有則呼叫iterator.next()獲取繼續遍歷,沒有則結束遍歷。即通過String each:list遍歷相當於在List中先建立一個迭代器,然後進行if(iterator.hasNext)判斷是否還含有元素,有則進行each = iterator.next()進行賦值遍歷。

該文章ArrayList移除元素時涉及的方法:

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 }
elementData為列表用於儲存資料的陣列,而modCount++為列表結構的修改次數,刪除元素不為null時看remove(Object o)中的第二個迴圈,由fastRemove(int index)可以看出列表每次刪除元素時修改此時modCount都會自增1。

ArrayList的iterator方法(Itr實現Iterator介面,是ArrayList的內部類,含iterator()方法的集合類都會返回相應的Iterator實現類):


 
public Iterator<E> iterator() {
    return new Itr();
}
該文章涉及的ArrayList迭代器Itr的主要內容如下(省略remove與forEachRemaining方法):

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


    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
由Itr中的checkForComodification()方法可以看出當列表的修改次數(預設值為0)與希望的修改次數(0)不相等時都會丟擲ConcurrentModificationException異常,而cursor為下一個返回值的列表索引,如下例中遍歷到"f"時cursor為5等於列表size 5。

原始碼看到這應該清楚的知道遍歷list時進行刪除丟擲錯誤的原因是因為modCount != expectedModCount,而刪除倒數第二個不拋錯的原因就在於迭代器獲取元素前的hasNext()判斷,當遍歷到倒數第二個元素並刪除該元素時將使列表的size-1並等於cursor,此時hasNext()返回false所以不再呼叫next()方法呼叫checkForComodification()進行修改驗證。

以下是個人的測試程式碼,由於刪除的元素位於ArrayList的倒數第二個,所有並不會拋併發修改異常:

package per.test.list;

import com.google.common.collect.Lists;
import org.junit.Test;

import java.util.List;
import java.util.Objects;

/**
 * 建立人:Wilson
 * 描述:
 * 建立日期:2017/10/3
 */
public class ListTest {
    @Test
    public void listRemoveTest() {
        List<String> list = Lists.newArrayList("a", "b", "c", "d", "e", "f");
        for (String each : list) {
            if(Objects.equals(each,"e")){
                list.remove(each);
            }
        }
    }
}