如何在对集合迭代时正确地修改集合

设想你有一个简单的需求:在一个ArrayList中的某个位置插入一个新的元素,于是你写下了如下代码

ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
Iterator<String> it = list.iterator();
while(it.hasNext()){
    if("cc".equals(it.next())){
        list.add("kk"); // 插入一个新的元素
    }
}

发现报错:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at org.zyf.Main.main(Main.java:20)

说明在调用it.next()的时候抛出了ConcurrentModificationException异常.

出错原因:

ArrayList类继承了抽象类AbstractList,所以ArrayList拥有一个成员变量modCount,记录了对ArrayList的操作次数:

// AbstractList.java

/**
 * The number of times this list has been structurally modified.
 ...
 ...
*/
protected transient int modCount = 0;

每次对 ArrayList 的 add 和 remove 方法的成功调用,都会使 modCount 自增。

阅读源码可以发现,在对 ArrayList 使用迭代器进行迭代的时候,使用的其实是实现了 Iterater 接口的 ArrayList 内部类 Itr,

// ArrayList.java

public Iterator<E> iterator() {
        return new Itr();
}
...
private class Itr implements Iterator<E> {
    ...
}

迭代时,我们调用 Itr.next(),而这个方法会调用 checkForComodification() 方法:

// ArrayList.Itr

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];
}

再看看 checkForComodification() 做了什么:

// ArrayList.Itr

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

这个方法检查了 modCount 和 expectedModCount 的一致性,如果不一致,则抛出 ConcurrentModificationException 异常,这里就是报错的源头。

而 Itr 进行初始化的时候,其成员变量 expectedModCount 正是被初始化为了modCount 的值:

// ArrayList.java

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;
        ...
}

这说明在创建迭代器时expectedModCount和modCount就是一致的。

但是!在创建迭代器之后,任何对ArrayList的直接修改(绕过迭代器对其进行修改),都不会影响 expectedModCount!

文章开头代码中直接使用List.add()加入新元素,只会修改modCount的值,expectedModCount 并不会发生变化,所以无法通过一致性检查,导致了异常。

解决办法:

事情让一个“人”做 —— 引入新的迭代器:ListIterator,该迭代器提供了安全的add方法,可使对ArrayList的修改通过一致性检查。

// ArrayList.ListItr,实现了ListIterator接口

public void add(E e) {
    checkForComodification();

    try {
        int i = cursor;
        ArrayList.this.add(i, e);
        cursor = i + 1;
        lastRet = -1;
        expectedModCount = modCount; // 更新expectedModCount
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

迭代和添加操作都是靠ListIterator来完成的:

ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
	if("cc".equals(it.next())){
	it.add("kk");
}

tip:

如果是想要迭代时删除元素,不必使用ListIterater,调用Iterator的remove即可

ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
// 移除"cc"
Iterator<String> it = list.iterator();
while(it.hasNext()){
    if("cc".equals(it.next())){
        it.remove();
    }
}

你可能感兴趣的:(Java,踩坑记录,java,开发语言,后端)