1、問題發現java
在迭代集合元素時,若是對集合作add/remove操做,會拋出java.util.ConcurrentModificationException異常。this
以下代碼所示:code
package com.wbf.list; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); List<String> del = new ArrayList<String>(); del.add("5"); del.add("6"); del.add("7"); for (Iterator<String> iter = list.iterator(); iter.hasNext();) { String s = iter.next(); if (del.contains(s)) { list.remove(s); } } } }
執行上訴代碼,會拋出異常:xml
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at com.wbf.list.ListDemo.main(ListDemo.java:23)
這是爲何呢?對象
2、問題分析繼承
在集合中,如ArrayList,對它的作出修改操做(add/remove)時都會對modCount這個字段+1,modCount能夠看做一個版本號,每次集合中的元素被修改後,都會+1(即便溢出)。接下來看看ArrayList從父類AbsrtactList中繼承的iterator方法接口
public Iterator<E> iterator() { return new Itr(); }
這個方法返回內部類Itr的實例對象,類Iter實現了接口Iterator,代碼以下element
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; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
在內部類Itr中有一個屬性expectedModCount,其默認的值等於modCountrem
int expectedModCount = modCount;
因此當咱們調用集合類的iterator()方法時,返回迭代器對象時,expectedModCount被初始化爲modCountget
從內部類的代碼中能夠看出,它的remove()和next()方法中都調用了checkForComodification()方法,而這個方法作的事情就是判斷modCount是否等於expectedModCount,若是不等於就拋出ConcurrentModificationException
在示例代碼中,在進行集合迭代時,第一執行
String s = iter.next();
是不會有問題的,可是一旦知足條件,執行了
if (del.contains(s)) { list.remove(s); }
若是集合還有元素未迭代,再次執行
String s = iter.next();
時,就會拋出:java.util.ConcurrentModificationException異常
這個異常是執行Itr類的next()方法時調用checkForComodification()拋出的。之因此會拋異常是由於初始化的時候modCount是等於expectedModCount的,可是在執行了一次
if (del.contains(s)) { list.remove(s); }
後,modCount+1,在執行Itr類的next()方法時調用checkForComodification()發現modCount已經不等於expectedModCount了,因此拋出異常.
3、問題解決
1. 方法1
不要經過list.remove(s)來移除元素,而是iter.remove(),這樣就沒有了
爲何呢?看Itr類的remove()方法
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
從remove()方法的源碼中,咱們發現了十分重要的一行代碼
expectedModCount = modCount;
一切都豁然開朗了吧。
2. 方法2
另外準備一個list用來保存須要移除的元素,在迭代完畢後一次性移除便可
package com.wbf.list; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); List<String> del = new ArrayList<String>(); del.add("5"); del.add("6"); del.add("7"); List<String> needDel = new ArrayList<String>(); for (Iterator<String> iter = list.iterator(); iter.hasNext();) { String s = iter.next(); if (del.contains(s)) { //list.remove(s); //iter.remove(); needDel.add(s); } } list.removeAll(needDel); } }
這種方法就不用解釋了吧。
4、問題補充
補充1:
在經過for(int i=0; i<list.size(); i++)方式遍歷集合時,經過直接調用List類自身的remove()方法來移除元素,是徹底沒有問題的
package com.wbf.list; import java.util.ArrayList; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); for (int i=0; i<list.size(); i++) { list.remove(i); } } }
理由很簡單:雖然list.remove()會修改modCount的值,可是它沒有調用checkForComodification()方法來校驗modCount與expectedModCount是否相等,因此不會有java.util.ConcurrentModificationException異常的拋出
可是若是是經過foreach方式遍歷集合的話,是會拋出java.util.ConcurrentModificationException異常的,代碼以下:
package com.wbf.list; import java.util.ArrayList; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); for (String s : list) { list.remove(s); } } }
由於foreach實現原理是轉換爲Iterator.next()來執行的。既然調用了Iterator的next()方法,必然須要經過checkForComodification()方法來校驗modCount是否等於expectedModCount,此時因爲list.remove(s)的緣由,modCount與expectedModCount顯然是不相等的,因此會拋出異常。
補充3:
其實從源碼中能夠看出,只有Itr類的remove()和next()方法在執行過程當中調用了checkForComodification()來檢查modCount是否等於expectedModCount,對於好比:get(),add()等方法都是沒有作這項工做的,因此若是經過for(int i=0; i<list.size(); i++)遍歷集合的時候,往集合中添加新的元素,是不會拋出ConcurrentModificationException異常的
package com.wbf.list; import java.util.ArrayList; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); int count = 0; for (int i=0; i<list.size(); i++) { if (count < 3) { list.add("count" + count++); } System.out.println(list.get(i)); } } }
執行徹底沒有問題。
可是這裏必須將for(int i=0; i<list.size(); i++)與foreach區分開來
若是時foreach方式遍歷集合時往集合中添加元素,執行時卻會拋出java.util.ConcurrentModificationException異常,代碼以下:
package com.wbf.list; import java.util.ArrayList; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); int count = 0; //for (int i=0; i<list.size(); i++) { for (String s : list) { if (count < 3) { list.add("count" + count++); } System.out.println(s); } } }
這是爲啥呢?由於foreach實現原理是轉換爲Iterator.next()來執行的。既然調用了Iterator的next()方法,必然須要經過checkForComodification()方法來校驗modCount是否等於expectedModCount,此時因爲list.add("count" + count++)的緣由,modCount與expectedModCount顯然是不相等的,因此會拋出異常。