最近在看《阿里巴巴Java开发手册》时,(五)集合处理-7【强制】不要在foreach
循环里进行元素的remove/add
操做。这一条规则中,有一段很是有意思的代码java
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
实现机制以及抛出的代码行数,很容易得知其缘由。this
咱们知道,foreach
是Java
的一个语法糖,经过反编译测试类生成的代码,咱们知道它其实是由Iterator实现的。反编译的结果以下:spa
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()
操做)。code
找到报错的代码行,咱们再来分析报错的缘由。既然在String item = (String)var2.next();
报错,说明while循环的条件是经过了。也就是说,咱们在移除了最后一个元素后,迭代器居然并未感知到,hasNext()
依然返回true。咱们来看看ArrayList.Iterator
的hasNext()
是如何实现的:cdn
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()
的实现:blog
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
。element
咱们再看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循环不能进行元素的修改操做,而只能进行元素的遍历操做。