1. 程式人生 > >【Java】List遍歷時刪除元素的正確方式

【Java】List遍歷時刪除元素的正確方式

例子 rabl ava else element 一次 exp java 無法

當要刪除ArrayList裏面的某個元素,一不註意就容易出bug。今天就給大家說一下在ArrayList循環遍歷並刪除元素的問題。首先請看下面的例子:

import java.util.ArrayList;
public class ArrayListRemove 
{
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add(
"b"); list.add("c"); list.add("c"); remove(list); for (String s : list) { System.out.println("element : " + s); } } public static void remove(ArrayList<String> list) { } }

常見錯誤寫法:

一:

public static void remove(ArrayList<String> list) 
    {
        
for (int i = 0; i < list.size(); i++) { String s = list.get(i); if (s.equals("b")) { list.remove(s); } } }

結果:第二個“b”的字符串沒有刪掉。

二:

public static void remove(ArrayList<String> list) 
    {
        for (String s : list)
        {
            
if (s.equals("b")) { list.remove(s); } } }

結果:這種for-each寫法會報出並發修改異常:java.util.ConcurrentModificationException。

錯誤原因:先看下ArrayList中的remove方法,看入參為Object的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;
    }

執行路徑會到else路徑下最終調用faseRemove方法:

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; // Let gc do its work
    }

可以看到會執行System.arraycopy方法,導致刪除元素時涉及到數組元素的移動。針對錯誤寫法一,在遍歷第一個字符串b時因為符合刪除條件,所以將該元素從數組中刪除,並且將後一個元素移動(也就是第二個字符串b)至當前位置,導致下一次循環遍歷時後一個字符串b並沒有遍歷到,所以無法刪除。針對這種情況可以倒序刪除的方式來避免:

public static void remove(ArrayList<String> list) 
    {
        for (int i = list.size() - 1; i >= 0; i--) 
        {
            String s = list.get(i);
            if (s.equals("b")) 
            {
                list.remove(s);
            }
        }
    }

因為數組倒序遍歷時即使發生元素刪除也不影響後序元素遍歷。

實例二的錯誤原因。產生的原因卻是foreach寫法是對實際的Iterable、hasNext、next方法的簡寫,問題同樣處在上文的fastRemove方法中,可以看到第一行把modCount變量的值加一,但在ArrayList返回的叠代器:

public Iterator<E> iterator() {
    return new Itr();
}

這裏返回的是AbstractList類內部的叠代器實現private class Itr implements Iterator,看這個類的next方法:

public E next() {
        checkForComodification();
        try {
            E next = get(cursor);
            lastRet = cursor++;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

第一行checkForComodification方法:

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

這裏會做叠代器內部修改次數檢查,因為上面的remove(Object)方法修改了modCount的值,所以才會報出並發修改異常。要避免這種情況的出現則在使用叠代器叠代時(顯示或for-each的隱式)不要使用ArrayList的remove,改為用Iterator的remove即可。

public static void remove(ArrayList<String> list) 
    {
        Iterator<String> it = list.iterator();
        while (it.hasNext()) 
        {
            String s = it.next();
            if (s.equals("b")) 
            {
                it.remove();
            }
        }
    }

【Java】List遍歷時刪除元素的正確方式