探究List中foreach循环里进行元素的remove操作引起的异常
关键词:
for
foreach
iterator.hasNext()
iterator.next()
iterator.remove()
list.remove
modCount
expectedModCount
快速失败机制(fail-fast)
一、问题引入
上图为阿里巴巴java开发手册的开发规范,foreach中为什么不能进行remove操作?反例中“1”删除不会报错,“2”删除出现ConcurrentModificationException异常?接下来,探究其原因
二、问题分析
要弄清这些问题,首先要了解集合中的remove方法和迭代器中的remove方法的区别(ArrayList、Iterator)
①ArrayList中的remove方法(如下图所示):
每次进入remove()方法,都会调用fastRemove(),然后modCount++;
②Iterator中的remove方法(如下图所示):
迭代器调用的其实是ArrayList中内部类Itr的remove方法,每次进入remove()方法,都会调用checkForComodification(),然后判断 if (modCount != expectedModCount),不相等就会抛出ConcurrentModificationException;
③modCount和expectedModCount
进一步就要搞清楚modCount和expectedModCount是什么,在哪赋值的
modCount是在抽象类AbstractList中定义的,所以对集合的操作都会改变modCount的值,和java中的快速失败机制有关(fail-fast)(一种错误检测机制,不需要通过复杂的算法)。
expectedModCount是在内部类Itr中定义的,初始化时会将modCount值赋给expectedModCount;
所以最终结论, foreach 循环里进行元素的remove操作所引发的异常,本质就是调用了checkForComodification()方法,判断modCount != expectedModCount为true时而抛出的ConcurrentModificationException(并发修改异常);
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
三、迭代器Iterator的执行原理
①foreach和Iterator
先分析foreach和Iterator之间的关系,打开编译后的.class文件(如下图):
编译后的.class文件
由图可以看出,foreach其实就是使用了Iterator来进行遍历的,出现ConcurrentModificationException原因就是在迭代器中使用了ArrayList中的remove方法,在迭代器外部修改了modCount的值,导致和expectedModCount值不相等抛出异常。
回到开发手册上的案例,为什么反例中“1”删除不会报错,“2”删除出现ConcurrentModificationException异常?
②Iterator中的方法,hasNext()、next()、remove()
先看迭代器中的三个方法hasNext()、next()、remove()
③迭代器Iterator中的执行原理
foreach循环的test01()改写为下图所示代码:
@Test
public void test01() {
List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String item = (String)var2.next();
if ("1".equals(item)) {
list.remove(item);
}
}
System.out.println(list); // 2
}
cursor为下一个元素的索引,size为集合的大小,接着调用.next()方法,cursor变为1;
执行步骤分析:
cursor=0;size=2
cursor != size 为true,进入循环
if条件为true,删除"1",
由于是调用ArrayList中的remove方法,所以modCount++,即modCount - expectedModCount = 1
此时,list.size() = 1,cursor = 1(调用迭代器中的next()方法,cursor+1)
再次判断while中条件,为false,跳出循环,不会调用next(),自然也就不会调用checkForComodification();
foreach循环的test02()改写为下图所示代码:
@Test
public void test02() {
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);
}
}
System.out.println(list);
}
执行步骤分析:
cursor = 0;size = 2
cursor != size 为true,进入循环
调用next()后得到item = 1,cursor变为1
if条件为false,进入第二次循环,此时,cursor = 1,size = 2;
调用next()后得到item = 2,cursor变为2;
如果此时调用的是Iterator中的remove,
会有expectedModCount = modCount;
调用ArrayList中的remove方法,所以modCount++,即modCount - expectedModCount = 1;
并且此时list.size() = 1;
再次进入while循环,
list.size() = 1,cursor = 2,return cursor != size 为true,进入下一步,
调用.next(),进入checkForComodification()方法中,
modCount != expectedModCount为true,抛出异常ConcurrentModificationException;