前言
今天写LeetCode遇到一道题,我想利用作为方法参数的一个集合作为返回的值,来达到节省空间的目的:
public List<Interval> merge(List<Interval> intervals) {}
意思就是,我想对集合intervals进行修改,然后返回值就传修改后的intervals。
然后顺势就联想到了并发修改异常,之前只是知道个概念,并没有仔细思考过。今天就来分析一下ConcurrentModificationException。
ConcurrentModificationException
ConcurrentModificationException是开发中一个常见的异常,多发生于对一个Collection进行边遍历边做影响size变化的操作时,比如说遍历集合同时向集合中添加新的元素。
举例
下面我们进行三种操作:
1.利用for循环遍历集合的同时添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 2) {
list.add(10);
}
}
没有问题。
2.利用迭代器遍历集合的同时添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next() == 2) {
list.add(10);
}
}
出现了问题,编译器提示:Exception in thread “main” java.util.ConcurrentModificationException。
3.利用超级for循环遍历集合的同时添加元素
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
for (int num : list) {
if (num == 2) {
list.add(10);
}
}
同样出现了问题,编译器提示:Exception in thread “main” java.util.ConcurrentModificationException。
分析
以上使用的是ArrayList,那么我们来看一下ArrayList中关于迭代器的源码(Android SDK 23中的Open JDK源码):
@Override public Iterator<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator implements Iterator<E> {
……
/** The expected modCount value */
private int expectedModCount = modCount;
@SuppressWarnings("unchecked") public E next() {
ArrayList<E> ourList = ArrayList.this;
int rem = remaining;
if (ourList.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (rem == 0) {
throw new NoSuchElementException();
}
remaining = rem - 1;
return (E) ourList.array[removalIndex = ourList.size - rem];
}
……
}
调用iterator()会返回一个ArrayListIterator对象,ArrayListIterator初始化的时候,会将外部类ArrayList(其实是ArrayList的父类AbstractList中的)的成员变量modCount的值赋给expectedModCount。
然而我们向集合中添加元素的时候,改变了modCount的值:
@Override public boolean add(E object) {
Object[] a = array;
int s = size;
if (s == a.length) {
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
a[s] = object;
size = s + 1;
modCount++;
return true;
}
而expectedModCount的值没有变,这时候再调用next(),会走进如下判断分支:
if (ourList.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
抛出ConcurrentModificationException。
至于超级for循环遍历呢?其实for-each是个语法糖,编译器会把它转化成迭代器遍历,所以同样会出错。
iterator()不行,我们其实还可以使用listIterator(),它是ArrayList的父类AbstractList中的方法,它返回的是一个FullListIterator对象。我们增删元素就利用FullListIterator的remove()和add(),如下:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
ListIterator<Integer> listIterator = list.listIterator();
while (listIterator.hasNext()) {
if (listIterator.next().equals(2)) {
listIterator.add(10);
}
}
不会出现问题。
首先它的remove()和add()中调用的就是AbstractList的remove()和add()。
其次它会将expectedModCount的值与modCount的值进行同步,具体可以去查看源码,这里就不做分析了。