在集合输出的时候有提过:在对ArrayList和Vector进行迭代输出的同时不要修改集合中的元素(添加或者删除)。否则就会抛
出java.util.ConcurrentModificationException异常。本文主要解释此异常出现的原因以及解决办法。
我们首先来看一下这种异常是怎么出现的,先看下面的代码
public class Main{ public static void main(String[] args){ List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); list.add("D"); Iterator<String> iterator = list.iterator(); String str = null; while (iterator.hasNext()){ str = iterator.next(); if("A".equals(str)){ list.remove("A"); continue; } System.out.println(str); } } }
这段代码的大致意思是:在迭代输出的时候,如果发现有元素A,那么就直接删除掉。
我们可以看到这里抛出了java.util.ConcurrentModificationException异常,我们来一步一步的看源码
1:首先可以看到异常的最外层抛出位置为Main.main(),是调用iterator.next()方法的时候出现了异常。
2:在往上看异常的抛出位置,在调用next()方法时,首先调用了一个名为checkForComodification()的方法,这个方法定义在一个名叫Itr类的内部。
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
现在,首先看一下ArrayList中的iterator()方法的实现。
public Iterator<E> iterator() { return new Itr(); }
这里返回了一个Itr类的对象,Itr类是ArrayList的私有内部类,以下是Itr类的源码。
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(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } /** * An optimized version of AbstractList.ListItr */ private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { super(); cursor = index; } public boolean hasPrevious() { return cursor != 0; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } @SuppressWarnings("unchecked") public E previous() { checkForComodification(); int i = cursor - 1; if (i < 0) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i; return (E) elementData[lastRet = i]; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.set(lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }
在checkForComodification()方法中,出现了名为modCount和expectedCount的变量,如果发现这两个值不相等的话就会抛出异常。
expectedmodCount:对ArrayList修改次数的期望值,定义在Itr类中。
modCount:对List的实际修改次数,是abstarctList的成员变量,每次进行add()或者remove()操作都会使modCount++
现在总结一下:每次调用iterator的next()方法时,都会自动检测expectedmodCount和modCount值,如果不相等就直接抛出ConcurrentModificationException异常。
在迭代输出时删除元素如果使用List接口的remove()方法,就会对modCount的值进行改动
public E remove(int index) { rangeCheck(index); modCount++;//modCount加1 E oldValue = elementData(index); 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 return oldValue; }
可以看到modCount的值进行了改动,但是此时expectedModCount的值并没有变化,所以当再次调用iterator.next()时,因为两个值不相等,异常就会被抛出。
但是,如果在删除的时候调用的是iterator的remove()方法,(注意,这里实际上调用的是其实现类Itr的remove()方法)
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet);//先调用List的remove()方法 cursor = lastRet; lastRet = -1; expectedModCount = modCount;//可以看到,这里把modCount的值赋给了expectedModCount,值相等了 } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
Iterator接口的实现子类Itr覆写了remove()方法,此方法中先调用了List的remove()方法,然后多了一行赋值语句,使modCount的值和expectedModCount的值相等。这样,在接下来的判断中,就不会抛出异常了。
总结一下,如果要在迭代输出的过程中删除集合中的元素,应该使用Iterator的实现子类Itr的remove()方法,就可以使modCount = expectedModCount,避免异常。但是,如果使用List子类的remove()方法,就会抛出ConcurrentModificationException异常。