关于这个比较基础的问题本来是不想记录的。但是为啥还是要记录呢,原因我就不多说了。
关于ConcurrentModificationException,很多人第一次见到这个异常都是因为对List进行foreach操作,然后删除元素。
foreach的实现原理其实就是iterator,可能很多人都会想使用iterator就不会抛这个异常。抛不抛这个异常其实取决于调用的List的remove方法还是iterator的remove方法。我们来看看两个remove方法有什么不一样。
// List的remove方法
public E remove(int index) {
rangeCheck(index);
modCount++;
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;
}
// iterator的remove方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet); //这里调用的是list的remove方法
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount; // 关键是这里
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
iterator的remove方法里除了调用List的remove方法之后多了一个操作就是expectedModCount = modCount。然后有个checkForComodification方法是这样的
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
foreach循环之所以会抛ConcurrentModificationException异常就是调用的是List的remove方法导致expectedModCount和modCount不一致。如果使用iterator遍历,使用的remove方法也是List的remove方法的话,也会出现同样的问题。
所有避免expectedModCount和modCount不一致导致的ConcurrentModificationException异常的方法就是使用iterator的remove方法。
当然,使用普通的for循环是不会抛这个异常的,因为这个异常不是List的remove方法抛出的。使用普通的for循环来删除元素也是有技巧的。
public static void main(String[] args) {
List<Integer> lst = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5,6,7,8,9,10));
for(int i = lst.size() - 1 ; i >= 0 ; i--) {
System.out.println(lst.get(i));
lst.remove(i);
}
}
上面的代码可以正常运行。并且输出和预料的一样是10,9,8,7...3,2,1
public static void main(String[] args) {
List<Integer> lst = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5,6,7,8,9,10));
for(int i = 0 ; i < lst.size() ; i++) {
System.out.println(lst.get(i));
lst.remove(i);
}
}
这个代码就不一样了,会输出1,3,5,7,9 原因就是删除1之后,2挪到了数组下标为0的位置,但是i已经变成1了,而1的位置已经不是2是3了,所以2就错过了。4,6,8,10也是同样的道理