Alibaba Java开发手册中指出,不要在 foreach 循环里进行元素的 remove/add 操作,那么进行这些操作到底会产生什么样的结果呢?
我们先来写个实例看看。
public class TestForeach {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
for (Integer number : numbers) {
numbers.remove(number);
}
}
}
再来看看执行结果:
根据报错提示,可以定位到报错的位置在java.util.ArrayList$Itr.next(ArrayList.java:997)
,并最终定位到java.util.ArrayList$Itr.checkForComodification()
那么我们跳转过去看看源码。
可以看到,我们定位到了ArrayList
中内部类Itr
类中的next()
方法,类Itr实现了迭代器接口Iterator
,因此可以用来遍历集合的元素。
那么为什么我们使用增强for循环时,会进入到迭代器的next()
方法中呢?我们来看看反编译后的class文件,揭开其真实面目。
原来,源代码在编译之后,实际上是使用迭代器来帮我们进行遍历的,这就足以说明为什么产生报错的地方是在Itr的next()方法处了。接下来我们再来查看next()方法。
果然,在方法的第一行处执行了,checkForComodification()
,也就是最终报错的地方,从方法名可以大概知道,该方法的目的是检测集合是否被修改过。我们再跟进看看:
发现方法中就是在判断两个类变量值是否相等,那么这两个变量又来自何处,我们继续跟进源码:
从上面两张图可以得知,modCount
来自AbstractList
类,而ArrayList
继承自AbstractList
,因此可以直接使用该变量,该字段的注释的表明,该字段记录了集合结构被修改的次数,而迭代器被创建的时候将该字段赋值给了expectedModCount
字段,这使得一开始它们的值是相等的。
这里猜测,我们在对集合元素进行删除操作时,修改了modCount
的值,从而导致两个变量的值不相等,我们来一探究竟。
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
最终在fastRemove()
执行了modCount++
操作,因此而导致了抛出ConcurrentModificationException。
那么在遍历集合时,我们正确的删除姿势是什么样的呢?
public class TestForeach {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
}
}
正确的姿势就是使用迭代器的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();
}
}
在该方法中,执行了expectedModCount = modCount;
操作,从而避免了ConcurrentModificationException异常。