今天同事寫了幾行類似這樣的代碼:
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檢查,以防有變。如果有變,則以異常拋出,所以就出現了上面的異常。