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显然是不相等的,因此会抛出异常。