1. 程式人生 > >集合類的remove(obj)和iterator的remove方法差別

集合類的remove(obj)和iterator的remove方法差別

工作中碰到個ConcurrentModificationException。程式碼如下:
List list = ...;
for(Iterator iter = list.iterator(); iter.hasNext();) {
    Object obj = iter.next();
    ...
    if(***) {
        list.remove(obj);
    }
}
在執行了remove方法之後,再去執行迴圈,iter.next()的時候,報java.util.ConcurrentModificationException(當然,如果remove的是最後一條,就不會再去執行next()操作了)


下面來看一下原始碼
public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

public interface Collection<E> extends Iterable<E> {
    ...
    Iterator<E> iterator();
    boolean add(E o);
    boolean remove(Object o);
    ...
}

這裡有兩個remove方法

接下來來看看AbstractList

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {  
//AbstractCollection和List都繼承了Collection
    protected transient int modCount = 0;
    private class Itr implements Iterator<E> {  //內部類Itr
        int cursor = 0;
        int lastRet = -1;
        int expectedModCount = modCount;

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

        public E next() {
            checkForComodification();  //特別注意這個方法
            try {
                E next = get(cursor);
                lastRet = cursor++;
                return next;
            } catch(IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet == -1)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);  //執行remove物件的操作
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;  //重新設定了expectedModCount的值,避免了ConcurrentModificationException的產生
            } catch(IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)  //當expectedModCount和modCount不相等時,就丟擲ConcurrentModificationException
                throw new ConcurrentModificationException();
        }
    }    
}


remove(Object o)在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++;  //只增加了modCount
    ....
}

所以,產生ConcurrentModificationException的原因就是:
執行remove(Object o)方法之後,modCount和expectedModCount不相等了。然後當代碼執行到next()方法時,判斷了checkForComodification(),發現兩個數值不等,就丟擲了該Exception。
要避免這個Exception,就應該使用remove()方法。

這裡我們就不看add(Object o)方法了,也是同樣的原因,但沒有對應的add()方法。一般嘛,就另建一個List了


下面是網上的其他解釋,更能從本質上解釋原因:
Iterator 是工作在一個獨立的執行緒中,並且擁有一個 mutex 鎖。 Iterator 被建立之後會建立一個指向原來物件的單鏈索引表,當原來的物件數量發生變化時,這個索引表的內容不會同步改變,所以當索引指標往後移動的時候就找不到要迭代的物件,所以按照 fail-fast 原則 Iterator 會馬上丟擲 java.util.ConcurrentModificationException 異常。
所以 Iterator 在工作的時候是不允許被迭代的物件被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除物件, Iterator.remove() 方法會在刪除當前迭代物件的同時維護索引的一致性。

迭代刪除(Iterator.remove())時的java.lang.IllegalStateException原因及解決辦法

先說明一下,下面總結的原因是根據網上的資料及實測後的個人觀點,有什麼不對的地方請各位糾正。

言歸正傳,我們在用迭代刪除(Iterator.remove())時,可能會因為沒有“it.next();”這一行, 丟擲java.lang.IllegalStateException異常,原因是通過Iterator來刪除集合中某一個不滿足條件的元素時,首先需要使用next方法迭代出集合中的元素 ,然後才能呼叫remove方法,否則集合可能會因為對同一個Iterator remove了多次而丟擲java .lang.IllegalStateException異常。

可以看看下面的例項:

  1. Integer[] selects = new Integer[]{1,2,3};  
  2. Integer[] input = new Integer[]{0,2,3,8,9};  
  3. int index = 0;  
  4. for (Iterator<Object> it = input.iterator(); it.hasNext();) {  
  5.     /* 
  6.      * 沒有it.next();這一行, 就會丟擲java.lang.IllegalStateException異常,原因<br>“ 
  7.      * 要刪除集合中某一個不滿足條件的元素 
  8.      * ,通過Iterator來刪除,首先需要使用next方法迭代出集合中的元素 
  9.      * ,然後才能呼叫remove方法,否則集合可能丟擲java 
  10.      * .lang.IllegalStateException異常。” 
  11.      */
  12.     it.next();  
  13.     for (int select : selects) {  
  14.         if (select == index) {  
  15.             it.remove();  
  16.         }  
  17.     }  
  18.     index++;  
  19. }  
處於併發訪問的問題最好用iterator
public class CollectionModifyExceptionTest {
public static void main(String[] args) {
Collection users = new ArrayList();

//new ArrayList();
users.add(new User("a",28));
users.add(new User("b",25));
users.add(new User("c",31));
Iterator itrUsers = users.iterator(); 
while(itrUsers.hasNext()){
User user = (User)itrUsers.next();
if("b".equals(user.getName())){
//users.remove(user); error 會有異常
itrUsers.remove();//ok  
} else {
System.out.println(user);
}
}
}