foreach循环(加强for循环)的坑 - ConcurrentModificationException

        最近在看《阿里巴巴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实现机制

        咱们知道,foreachJava的一个语法糖,经过反编译测试类生成的代码,咱们知道它其实是由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.IteratorhasNext()是如何实现的: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()返回falsewhile循环结束;而当咱们删除第2个元素时,cursor的值是2,与当前ArrayList.size(1)不相等,所以hasNext()返回trueelement

        咱们再看next()为何抛出异常。ArrayList.Itrnext()操做中,首先检查modCount是否等于expectedModCount,若是不相等,则抛出ConcurrentModificationException,程序中的异常也正是在此处抛出的。expectedModCountArrayList.Itr维护,而咱们程序中用ArrayList.remove()移除元素,修改的是ArrayList.modCount,这两个值显然不相等(expectedModCount == 0modCount == 1)。正是因为hasNext()错误地返回了true,致使咱们调用next()而抛出了异常。开发

结论

        从上文的分析中咱们能够得出结论,因为foreach语法糖的特性,它并非严格意义上的java语法,不能像Java标准语法那样灵活地使用。从其生成的代码来看,foreach循环不能进行元素的修改操做,而只能进行元素的遍历操做。

相关文章
相关标签/搜索