集合遍历元素并删除的正确姿势

Alibaba Java开发手册中指出,不要在 foreach 循环里进行元素的 remove/add 操作,那么进行这些操作到底会产生什么样的结果呢?

我们先来写个实例看看。

public class TestForeach {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        for (Integer number : numbers) {
            numbers.remove(number);
        }
    }
}

再来看看执行结果:

集合遍历元素并删除的正确姿势_第1张图片
根据报错提示,可以定位到报错的位置在java.util.ArrayList$Itr.next(ArrayList.java:997),并最终定位到java.util.ArrayList$Itr.checkForComodification()那么我们跳转过去看看源码。

集合遍历元素并删除的正确姿势_第2张图片
可以看到,我们定位到了ArrayList中内部类Itr类中的next()方法,类Itr实现了迭代器接口Iterator,因此可以用来遍历集合的元素。

那么为什么我们使用增强for循环时,会进入到迭代器的next()方法中呢?我们来看看反编译后的class文件,揭开其真实面目。

集合遍历元素并删除的正确姿势_第3张图片
原来,源代码在编译之后,实际上是使用迭代器来帮我们进行遍历的,这就足以说明为什么产生报错的地方是在Itr的next()方法处了。接下来我们再来查看next()方法。
集合遍历元素并删除的正确姿势_第4张图片
果然,在方法的第一行处执行了,checkForComodification(),也就是最终报错的地方,从方法名可以大概知道,该方法的目的是检测集合是否被修改过。我们再跟进看看:


发现方法中就是在判断两个类变量值是否相等,那么这两个变量又来自何处,我们继续跟进源码:

集合遍历元素并删除的正确姿势_第5张图片
集合遍历元素并删除的正确姿势_第6张图片
从上面两张图可以得知,modCount来自AbstractList类,而ArrayList继承自AbstractList,因此可以直接使用该变量,该字段的注释的表明,该字段记录了集合结构被修改的次数,而迭代器被创建的时候将该字段赋值给了expectedModCount字段,这使得一开始它们的值是相等的。

这里猜测,我们在对集合元素进行删除操作时,修改了modCount的值,从而导致两个变量的值不相等,我们来一探究竟。

public E remove(int index) {
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);

    return oldValue;
}
private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}

最终在fastRemove()执行了modCount++操作,因此而导致了抛出ConcurrentModificationException

那么在遍历集合时,我们正确的删除姿势是什么样的呢?

public class TestForeach {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        Iterator<Integer> iterator = numbers.iterator();
        while (iterator.hasNext()) {
            iterator.next();
            iterator.remove();
        }
    }
}

正确的姿势就是使用迭代器的remove()方法。

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

在该方法中,执行了expectedModCount = modCount;操作,从而避免了ConcurrentModificationException异常。

你可能感兴趣的:(java,java)