一、foreach中remove,抛异常
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
for (String item : list) {
list.remove(item);
}
}
执行以上程序,会抛出ConcurrentModificationException异常,深究其原因发现原来
for (String item : list) {
list.remove(item);
}
实际上相当于
Iterator<String> itr = list.iterator();
while(itr.hasNext()){
String item = itr.next();
list.remove(item);
}
list.remove(item)方法中调用了fastRemove()方法,它修改了modCount的值
private void fastRemove(int index) {
modCount++;
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
}
而itr.next()中会校验modCount和expectedModCount是否相等,不相等就抛出ConcurrentModificationException异常
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];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
expectedModCount是迭代器对象中的一个变量,它的初始值等于modCount
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;
...
}
现在执行了list.remove(item),modCount发生变化了,而expectedModCount没有变化,所以itr.next()中校验的时候就抛异常了。
二、迭代器中remove,不抛异常
为什么使用迭代器remove就不会发生异常呢
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
itr.next();
itr.remove();
}
}
这是因为在这里调用了迭代器自有的remove方法,我们发现这个方法的最后会把expectedModCount重置为modCount,因此下次循环执行itr.next()的时候,校验就能通过
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();
}
}
三、总结
为什么Java要在itr.next()中做这么一层校验呢?因为我们创建了一个迭代器,该迭代器和要遍历的集合的内容是紧耦合的,意思就是这个迭代器对应的集合内容就是当前的内容,我门肯定不会希望在遍历集合的时候,还有线程在向我们的集合里新增/删除数据,所以Java用了这种简单的处理机制来禁止遍历时修改集合。
综上所述,在foreach中使用list.remove(item)会抛异常的根本原因是因为把list对象的方法和迭代器对象的方法混合使用了,如果都使用迭代器对象的方法,就不会出现异常。所以建议在循环中有删除元素的需求时,尽量使用迭代器来实现。更好的方法是再创建一个list,来保存想要的元素,这样子不会破坏原有list。