常常會有人這麼對 list 進行遍歷,錯而不自知。java
示例代碼以下:this
public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); for (String str : list) { if ("aaa".equals(str)) { list.remove("aaa"); } } }
以上代碼執行致使的報錯信息以下:spa
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at demo.service.impl.test.main(test.java:14)
網上有不少博客對此都作了說明,這篇文章經過比較淺顯易懂的方式說明報錯產生的緣由。code
在 list.add 代碼執行時,有一個變量發生改變了,那就是 modCount。在代碼中 list.add 共執行4次,因此 modCount 的值爲 4。element
注:list 的 add()、remove() 和 clear() 都會改變 modCount 值。rem
for (String str : list) 調用的是 ArrayList 中內部類 Itr,Itr 是對 Iterator 的實現。而在 Iterator 開始前,會先執行 int expectedModCount = modCountget
此時 expectedModCount 和 modCount 均爲 4源碼
在此處先看一下會報錯的緣由,如下是源碼:博客
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
即 modCount 與 expectedModCount 不相等了,因此報錯。it
有人可能會跟我有同樣的想法,爲何 list.remove("aaa") 時,不把 expectedModCount = modCount 從新賦值一次。實際上是有的,只是調用的方法錯了。
例子中 list.remove("aaa") 調用的 remove 源碼以下:
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 fastRemove(index); return true; } } return false; }
而使 modCount 的值改變的是其中的 fastRemove 方法。
fastRemove 源碼以下:
private void fastRemove(int index) { // 此處 modCount + 1 modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
而真正使 expectedModCount = modCount 執行的源碼以下:
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } }
此代碼在內部類 Itr 中。
這也就是爲何會說,若是 list 在循環中有刪除操做,最好用 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 { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet < 0) 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(); } }
本文爲雲棲社區原創內容,未經容許不得轉載。