Java ArrayList刪除特定元素的方法

codeceo發表於2015-03-24

ArrayList是最常用的一種java集合,在開發中我們常常需要從ArrayList中刪除特定元素。有幾種常用的方法:

最樸實的方法,使用下標的方式:

ArrayList<String> al = new ArrayList<String>();
    al.add("a");
    al.add("b");
    //al.add("b");
    //al.add("c");
    //al.add("d");

    for (int i = 0; i < al.size(); i++) {
        if (al.get(i) == "b") {
            al.remove(i);
            i--;
        }
    }

在程式碼中,刪除元素後,需要把下標減一。這是因為在每次刪除元素後,ArrayList會將後面部分的元素依次往上挪一個位置(就是copy),所以,下一個需要訪問的下標還是當前下標,所以必須得減一才能把所有元素都遍歷完

還有另外一種方式:

ArrayList<String> al = new ArrayList<String>();
    al.add("a");
    al.add("b");
    al.add("b");
    al.add("c");
    al.add("d");

    for (String s : al) {
        if (s.equals("a")) {
            al.remove(s);
        }
    }

此處使用元素遍歷的方式,當獲取到的當前元素與特定元素相同時,即刪除元素。從表面上看,程式碼沒有問題,可是執行時卻報異常:

Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:886)
        at java.util.ArrayList$Itr.next(ArrayList.java:836)
        at com.mine.collection.TestArrayList.main(TestArrayList.java:17)

從異常堆疊可以看出,是ArrayList的迭代器報出的異常,說明通過元素遍歷集合時,實際上是使用迭代器進行訪問的。可為什麼會產生這個異常呢?打斷點單步除錯進去發現,是這行程式碼丟擲的異常:

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

modCount是集合修改的次數,當remove元素的時候就會加1,初始值為集合的大小。迭代器每次取得下一個元素的時候,都會進行判斷,比較集合修改的次數和期望修改的次數是否一樣,如果不一樣,則丟擲異常。檢視集合的remove方法:

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
}

可以看到,刪除元素時modCount已經加一,但是expectModCount並沒有增加。所以在使用迭代器遍歷下一個元素的時候,會丟擲異常。那怎麼解決這個問題呢?其實使用迭代器自身的刪除方法就沒有問題了

ArrayList<String> al = new ArrayList<String>();
al.add("a");
al.add("b");
al.add("b");
al.add("c");
al.add("d");

Iterator<String> iter = al.iterator();
while (iter.hasNext()) {
    if (iter.next().equals("a")) {
        iter.remove();
    }
}

檢視迭代器自身的刪除方法,果不其然,每次刪除之後都會修改expectedModCount為modCount。這樣的話就不會丟擲異常

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

建議以後操作集合類的元素時,儘量使用迭代器。可是還有一個地方不明白,modCount和expectedModCount這兩個變數究竟是幹什麼用的?為什麼集合在進行操作時需要修改它?為什麼迭代器在獲取下一個元素的時候需要判斷它們是否一樣?它們存在總是有道理的吧

其實從異常的型別應該是能想到原因:ConcurrentModificationException.同時修改異常。看下面一個例子

List<String> list = new ArrayList<String>();
// Insert some sample values.
list.add("Value1");
list.add("Value2");
list.add("Value3");

// Get two iterators.

Iterator<String> ite = list.iterator();

Iterator<String> ite2 = list.iterator();
 // Point to the first object of the list and then, remove it.

ite.next();

ite.remove();
/* The second iterator tries to remove the first object as well. The object does not exist and thus, a ConcurrentModificationException is thrown. */

ite2.next();

ite2.remove();

同樣的,也會報出ConcurrentModificationException。

相關文章