對JAVA集合進行遍歷刪除時務必要用迭代器

今天同事寫了幾行類似這樣的代碼:

public static void main(String args[]) {
    List<String> famous = new ArrayList<String>();
    famous.add("liudehua");
    famous.add("madehua");
    famous.add("liushishi");
    famous.add("tangwei");
    for (String s : famous) {
        if (s.equals("madehua")) {
            famous.remove(s);
        }
    }
}

運行出異常:

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)

at java.util.AbstractList$Itr.next(AbstractList.java:343)

at com.bes.Test.main(Test.java:15)

Java新手最容易犯的錯誤,對JAVA集合進行遍歷刪除時務必要用迭代器。切記。

其實對於如上for循環,運行過程中還是轉換成了如下代碼:

for(Iterator<String> it = famous.iterator();it.hasNext();){
         String s = it.next();
         if(s.equals("madehua")){
             famous.remove(s);
         }
     }

仍然採用的是迭代器,但刪除操作卻用了錯誤的方法。如將famous.remove(s)改成it.remove()

則運行正常,結果也無誤。

當然如果改成:

for (int i = 0; i < famous.size(); i++) {
            String s = famous.get(i);
            if (s.equals("madehua")) {
                famous.remove(s);
            }
        }

這種方法,也是可以完成功能,但一般也不這麼寫。

爲什麼用了迭代碼器就不能採用famous.remove(s)操作? 這種因爲ArrayList與Iterator混合使用時會導致各自的狀態出現不一樣,最終出現異常。

我們看一下ArrayList中的Iterator實現:

private class Itr implements Iterator<E> {
   /**
    * Index of element to be returned by subsequent call to next.
    */
   int cursor = 0;
   /**
    * Index of element returned by most recent call to next or
    * previous.  Reset to -1 if this element is deleted by a call
    * to remove.
    */
   int lastRet = -1;
   /**
    * The modCount value that the iterator believes that the backing
    * List should have.  If this expectation is violated, the iterator
    * has detected concurrent modification.
    */
   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);
       if (lastRet < cursor)
           cursor--;
       lastRet = -1;
       expectedModCount = modCount;
       } catch (IndexOutOfBoundsException e) {
       throw new ConcurrentModificationException();
       }
   }
   final void checkForComodification() {
       if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
   }
   }

基本上ArrayList採用size屬性來維護自已的狀態,而Iterator採用cursor來來維護自已的狀態。

當size出現變化時,cursor並不一定能夠得到同步,除非這種變化是Iterator主動導致的。

從上面的代碼可以看到當Iterator.remove方法導致ArrayList列表發生變化時,他會更新cursor來同步這一變化。但其他方式導致ArrayList變化,Iterator是無法感知的。ArrayList自然也不會主動通知Iterator們,那將是一個繁重的工作。Iterator到底還是做了努力:爲了防止狀態不一致可能引發的無法設想的後果,Iterator會經常做checkForComodification檢查,以防有變。如果有變,則以異常拋出,所以就出現了上面的異常。


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