爲什麼iterator,foreach遍歷時不能進行remove操作?

Exception in thread "main" java.util.ConcurrentModificationException 併發修改異常引發的思考!

1 foreach循環刪除元素

  ①list遍歷刪除元素時會報錯,比如下面刪除字符串"aa",也有遍歷不報錯的例子,看下面的例子

public class TestMain {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<String>();
        array.add("cc");
        array.add("aa");
        array.add("bb");
        array.add("aa");   
        for (String str : array) {
            if("aa".equals(str)){
                array.remove(str);
            }
        }

        System.out.println(array.size());

    }
}

console:   java.util.ConcurrentModificationException

  ②下面刪除字符串"aa"不會報錯

public class TestMain {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<String>();
        array.add("cc");
        array.add("aa");
        array.add("bb");
        for (String str : array) {
            if("aa".equals(str)){
                array.remove(str);
            }
        }

        System.out.println(array.size());

    }
}

console : 2

提出問題:爲什麼上面都是遍歷刪除,第二個確沒有報錯呢?

結論:其實原因很簡單,因爲第二個例子沒有走iterator的next方法,刪除了字符串"aa"之後,執行hasNext方法返回false直接退出遍歷了,hasNext中就是判斷cursor != size;此時的cursor是2,而size正好也是2,所以退出了遍歷。

而第一個例子刪除字符串"aa"之後,cursor=2,size=3,所以hasNext方法返回的true,會執行next方法。

:只要遍歷中remove了,expectedModCount和modCount就不相等了。

注2cursor 就類似遊標,這裏表示第幾次,size就是元素個數

同理:iterator的形式遍歷同foreach。

2 上面都是通過foreach的方式進行的,如果用普通for循環會怎麼樣

public class TestMain {
    public static void main(String[] args) {
        ArrayList<String> array = new ArrayList<String>();
        array.add("cc");
        array.add("aa");
        array.add("bb");
        array.add("aa");
        for(int i = 0;i < array.size();i++){
            if("aa".equals(array.get(i))){
              array.remove(i);
            }
        }
        System.out.println(array.size());

    }
}

console:  2 

  結論: 普通for循環可以正常刪除,他是根據索引進行刪除的,所以沒有影響。

 

根據報錯信息可以看到是進入checkForComodification()方法的時候報錯了,也就是說modCount != expectedModCount。具體的原因,是在於foreach方式遍歷元素的時候,是生成iterator,然後使用iterator遍歷。在生成iterator的時候,

會保存一個expectedModCount參數,這個是生成iterator的時候List中修改元素的次數。如果你在遍歷過程中刪除元素,List中modCount就會變化,如果這個modCount和exceptedModCount不一致,就會拋出異常,這個是爲了安全的考慮。

看看list的remove源碼:

1 private void fastRemove(int index) {
2         modCount++;
3         int numMoved = size - index - 1;
4         if (numMoved > 0)
5             System.arraycopy(elementData, index+1, elementData, index,
6                              numMoved);
7         elementData[--size] = null; // clear to let GC do its work
8     }

remove操作導致modCount++

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;
  }
  public E next() {
            checkForComodification();
        try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
        } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
        }
    }


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

 

 總結:  1 foreach遍歷,iterator遍歷都不能在遍歷的過程中使用list.remove或list.add操作,會報併發修改異常。

    2 iterator遍歷過程中如果需要刪除可以使用iterator提供的remove()方法。

    3 遍歷根據元素索引刪除是可行的。

以上屬於個人心得,不對之處還望大佬指出,如果對你有幫助會更加激勵我。  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章