最近在看《阿里巴巴Java开发手册》时,(五)集合处理-7【强制】不要在foreach
循环里进行元素的remove/add
操作。这一条规则中,有一段非常有意思的代码
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
执行这段代码,没有任何问题,foreach循环不会抛出任何异常,而且元素移除成功。
但是,当我们把if ("1".equals(item))
改成if ("2".equals(item))
时,程序会抛出ConcurrentModificationException。如下图所示:
通过分析foreach
实现机制以及抛出的代码行数,很容易得知其原因。
foreach实现机制
我们知道,foreach
是Java
的一个语法糖,通过反编译测试类生成的代码,我们知道它实际上是由Iterator实现的。反编译的结果如下:
List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator var2 = list.iterator();
while (var2.hasNext()) {
String item = (String)var2.next();
if ("2".equals(item)) {
list.remove(item);
}
}
结合程序抛出的异常栈信息,我们可以得知,异常是在String item = (String)var2.next();
这一行抛出的(只有这一行调用了next()
操作)。
异常原因
找到报错的代码行,我们再来分析报错的原因。既然在String item = (String)var2.next();
报错,说明while循环的条件是通过了。也就是说,我们在移除了最后一个元素后,迭代器竟然并未感知到,hasNext()
依然返回true。我们来看看ArrayList.Iterator
的hasNext()
是如何实现的:
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; // 修改次数,modCount来自于外部的ArrayList类;
// 而expectedModCount则由Itr记录
...
public boolean hasNext() {
return cursor != size; // size来自于外部的ArrayList类
}
...
}
ArrayList.iterator().hasNext()
的实现很简单,就是判断当前下标是否与数组大小相同。这就说明,当删除ArrayList
最后一个元素时,cursor != size
。为什么会这样呢?我们再来看一下next()
的实现:
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; // 修改次数,modCount来自于外部的ArrayList类;
// 而expectedModCount则由Itr记录
...
@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];
}
...
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我们看cursor
值 的变化,实际上,cursor
的值在每次进行元素值对比前,就已经跳到下一个位置。当我们删除第1个元素后,cursor
的值是1,与当前ArrayList.size(1)
是相等的,hasNext()
返回false
,while
循环结束;而当我们删除第2个元素时,cursor
的值是2,与当前ArrayList.size(1)
不相等,因此hasNext()
返回true
。
我们再看next()
为什么抛出异常。ArrayList.Itr
在next()
操作中,首先检查modCount
是否等于expectedModCount
,如果不相等,则抛出ConcurrentModificationException
,程序中的异常也正是在此处抛出的。expectedModCount
由ArrayList.Itr
维护,而我们程序中用ArrayList.remove()
移除元素,修改的是ArrayList.modCount
,这两个值显然不相等(expectedModCount == 0
而modCount == 1
)。正是由于hasNext()
错误地返回了true
,导致我们调用next()
而抛出了异常。
结论
从上文的分析中我们可以得出结论,由于foreach
语法糖的特性,它并不是严格意义上的java
语法,不能像Java标准语法那样灵活地使用。从其生成的代码来看,foreach循环不能进行元素的修改操作,而只能进行元素的遍历操作。