探究List中foreach循环里进行元素的remove操作引起的异常

  • 关键词:
  • 一、问题引入
  • 二、问题分析
  • ①ArrayList中的remove方法(如下图所示):
  • ②Iterator中的remove方法(如下图所示):
  • ③modCount和expectedModCount
  • 三、迭代器Iterator的执行原理
  • ①foreach和Iterator
  • ②Iterator中的方法,hasNext()、next()、remove()
  • ③迭代器Iterator中的执行原理



关键词:

for
foreach
iterator.hasNext()
iterator.next()
iterator.remove()
list.remove
modCount
expectedModCount
快速失败机制(fail-fast)


一、问题引入

java list for循环 index java 循环list remove_java

上图为阿里巴巴java开发手册的开发规范,foreach中为什么不能进行remove操作?反例中“1”删除不会报错,“2”删除出现ConcurrentModificationException异常?接下来,探究其原因

二、问题分析

要弄清这些问题,首先要了解集合中的remove方法和迭代器中的remove方法的区别(ArrayList、Iterator)

①ArrayList中的remove方法(如下图所示):

java list for循环 index java 循环list remove_迭代器_02


每次进入remove()方法,都会调用fastRemove(),然后modCount++;

②Iterator中的remove方法(如下图所示):

java list for循环 index java 循环list remove_List_03


迭代器调用的其实是ArrayList中内部类Itr的remove方法,每次进入remove()方法,都会调用checkForComodification(),然后判断 if (modCount != expectedModCount),不相等就会抛出ConcurrentModificationException;

③modCount和expectedModCount

进一步就要搞清楚modCount和expectedModCount是什么,在哪赋值的

java list for循环 index java 循环list remove_迭代器_04


modCount是在抽象类AbstractList中定义的,所以对集合的操作都会改变modCount的值,和java中的快速失败机制有关(fail-fast)(一种错误检测机制,不需要通过复杂的算法)。

java list for循环 index java 循环list remove_java_05


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文件(如下图):

java list for循环 index java 循环list remove_List_06


编译后的.class文件

java list for循环 index java 循环list remove_java_07


由图可以看出,foreach其实就是使用了Iterator来进行遍历的,出现ConcurrentModificationException原因就是在迭代器中使用了ArrayList中的remove方法,在迭代器外部修改了modCount的值,导致和expectedModCount值不相等抛出异常。

回到开发手册上的案例,为什么反例中“1”删除不会报错,“2”删除出现ConcurrentModificationException异常?

②Iterator中的方法,hasNext()、next()、remove()

先看迭代器中的三个方法hasNext()、next()、remove()

java list for循环 index java 循环list remove_List_08

③迭代器Iterator中的执行原理

java list for循环 index java 循环list remove_List_09


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
    }

java list for循环 index java 循环list remove_List_10


cursor为下一个元素的索引,size为集合的大小,接着调用.next()方法,cursor变为1;

java list for循环 index java 循环list remove_java_11


执行步骤分析:

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;