1. 程式人生 > >在使用迭代器遍歷集合時,為何不能使用集合的remove方法?

在使用迭代器遍歷集合時,為何不能使用集合的remove方法?

一、發現問題

在看到資料結構與演算法分析第三章時,看到這樣一段程式碼

public static void removeEvens(List<Integer> list) {
        for (Integer x : list) {
            if (x % 2 == 0) {
                list.remove(x);
            }
        }
    }

這段程式碼想完成的功能很清楚,就是要移除list集合中所有的偶數,但是這段程式會報錯,丟擲
ConcurrentModificationException異常。這是為什麼呢?書上是這樣說的,編譯器在看到一個實現了Interator介面的物件的增強for迴圈時,會自動地重寫,變成使用迭代器來遍歷集合。
上面for迴圈程式碼實際上會被翻譯為:

Iterator<Integer> iterator=list.iterator();
        while(iterator.hasNext()){
            Integer element=iterator.next();
            if(element%2==0){
                list.remove(element);
            }
        }

問題就很清楚了,在使用迭代器遍歷集合時,如果使用了集合的remove方法,則會丟擲ConcurrentModificationException異常。這是為什麼呢?書上只說了在使用Iterator時,如果對正在迭代的集合進行結構上的改變,即使用集合的add,remove,clear方法是,迭代器就不再合法。(不好理解)

二、解決問題

網上也有些寫的挺好的部落格,這裡自己再進行整理一下,以加深理解。

Iterator<Integer> iterator=list.iterator();

因為list是一個集合物件,它實現了Iterable介面,而該介面要求集合實現iterator的方法,返回值是一個Iterator型別。

public interface Iterator<AnyType>{
      boolean hasNext();
      AnyType next();
      void remove();

在ArrayList中,iterator方法會返回一個Itr型別的物件。

  public Iterator<E> iterator() {
        return new Itr();
    }
//內部類
 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
        //修改次數--->用於檢測在迭代期間被修改的情況,expectedModeCount初始值是modCount,通過expectedModeCount與modCount的比較來進行錯誤檢測
        int expectedModCount = modCount;

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

        @SuppressWarnings("unchecked")
        public E next() {
            //錯誤檢測
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            //elementData存在於外部類ArrayList中,它以陣列形式儲存著集合元素,通過外部類的隱式引用來使用elementData
            Object[] elementData = ArrayList.this.elementData;
            //如果此時的遊標大於集合元素的長度
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //遊標+1
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //迭代器的remove方法,實際上要靠集合的remove方法來實現,
                //值的注意的是:它對遊標進行了修改,並且物件expectedModCount進行了修正
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        //錯誤檢測方法,通過比較modCount與expectedModCount是否一致,不一致則丟擲異常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

迭代器的remove方法與集合的remove方法,最大的不同是,迭代器的remove方法中包括對遊標和expectedModCount的修正。
因為Iterator是在一個獨立的執行緒中工作的,它在new Itr()進行初始化時,會記錄當時集合中的元素,可以理解為記錄了集合的狀態,在使用集合的Remove方法對集合進行修改時,被記錄的集合狀態並不會與之同步改變,所以在cursor指向下一個要返回的元素時,可能會發生找不到的錯誤,即丟擲ConcurrentModificationException異常。

很明顯,如果使用迭代器提供的remove方法時,會對cursor進行修正,故不會出現錯誤,此外,還會修正expectedModCount,通過它來進行錯誤檢測(迭代過程中,不允許集合的add,remove,clear等改變集合結構的操作)。

The world is such small,it’s like when you turn around,you don’t know who you will see. The world is so big,as if when you turn around,you never know who will disappear.