ArrayList 迭代器学习笔记

我们先来看一段代码:

List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
    if ("str1".equals(s)) {
        list.remove(s);
    }
}

这段代码看起来好像没有什么问题,但是如果我们运行,就会抛出ConcurrentModificationException异常。

其实这不是特例,每当我们使用迭代器遍历元素时,如果修改了元素内容(添加、删除元素),就会抛出异常,由于 foreach 同样使用的是迭代器,所以也有同样的情况,大家可以自行试一下。我们来通过源码探究一下这个现象的根本原因。

ArrayList 源码阅读

下面是 ArrayList 的部分源码,可以明显的看到共有两个remove()方法,一个属于 ArrayList 本身,还有一个属于其内部类 Itr。

public class ArrayList<E> {
​
    void remove() {
        modCount++;  // 继承自AbstractList的属性,保存对其中元素的修改次数,每次增加或删除时加1
        // 具体删除操作代码
        //...
    }
  
    public Iterator<E> iterator() {
        return new Itr();
    }
​
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        // 在创建迭代器时将当前ArrayList的修改次数赋值给 expectedModCount 保存
        int expectedModCount = modCount;
​
        public boolean hasNext() {
            return cursor != size;
        }
​
        @SuppressWarnings("unchecked")
        public E next() {
            // 检查当前所在的 ArrayList 的 modCount 是否与创建 Itr 时的值一致,
            // 也就是判断获取了Itr迭代器后 ArrayList 中的元素是否被 Itr 外部的方法改变过。
            checkForComodification();
            // 具体的获取下一个元素的代码
            // ...
        }
​
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            // 同 next 中的 checkForComodification 方法
            checkForComodification();
​
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                // Itr 内部的删除元素操作,会更新 expectedModCount 值,而外部的则不会
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
​
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
}

ArrayList 类中有一个 modCount 属性,这个属性是继承子AbstractList,其保存了我们对 ArrayList 进行的的操作次数,当我们添加或者删除元素时,modeCount 都会进行对应次数的增加。

迭代器迭代时外部方法添加或删除元素

在我们使用 ArrayLis 的 iterator() 方法获取到迭代器进行遍历时,会把 ArrayList 当前状态下的 modCount 赋值给 Itr 类的 expectedModeCount 属性。如果我们在迭代过程中,使用了 ArrayList 的 remove()add()方法,这时 modCount 就会加 1 ,但是迭代器中的expectedModeCount 并没有变化,当我们再使用迭代器的next()方法时,它会调用checkForComodification()方法,即

final void checkForComodification() {
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

发现现在的 modCount 已经与 expectedModCount 不一致了,则会抛出ConcurrentModificationException异常。

迭代器迭代时,使用迭代器的方法添加或删除元素

但是如果我们使用迭代器提供的remove()方法,由于其有一个操作:expectedModCount = modCount;,会修改expectedModCount 的值,所以就不会存在上述问题。

总结

当我们使用迭代器迭代对象的时候,不要使用迭代器之外的方法修改元素,否则会报异常。如果我们要在迭代器迭代时进行修改,可以使用迭代器提供的删除等方法。或者使用其他方法遍历修改。

 


注意:

特殊情况下在迭代器过程中使用 ArrayList 的删除方法不会报异常,就是 只删除倒数第二个元素的时候,代码如下:

List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
    if ("str2".equals(s)) { // 必须只能是倒数第二个元素,这样才不会抛异常
        list.remove(s);
    }
}

其原因是迭代器的hasNext()方法:

public boolean hasNext() {
    return cursor != size;
}

在只删除了倒数第二个元素的时候,cursor 会与 size 相等,这样hasNext()方法会返回 false ,结束迭代,也就不会进入 next()方法中,进而执行checkForComodification()检查方法抛出异常。

 

 

posted on 2017-05-26 12:45  _路上  阅读(3714)  评论(0编辑  收藏  举报

导航