Java 中list(ArrayList)的foreach遍历方式调用list的remove删除元素,不抛出ConcurrentModificationException

185 阅读2分钟

关于list删除元素的当时有很多,具体可以参考下面这个博客。

https://blog.csdn.net/claram/article/details/53410175

里面提到了list的foreach遍历方式删除元素,会抛出ConcurrentModificationException。foreach是迭代器遍历的一种简写。
但是, 如果list中只有两个元素,删除第一个元素时,则不会抛出ConcurrentModificationException异常。

首先看一下迭代器的hasNext()next()方法源码。

        public boolean hasNext() {
        // 当前元素的位置不等于list的size代表还有元素没有遍历
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
        //检查是否有迭代器之外的modify
            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() {
        //expectedModCount 是迭代器自己维护的变量
        //modCount 是list中元素修改的数量
        //如果调用迭代器的方法对list操作,二者是相等的。例如下面的remove方法
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                //保证expectedModCount和modCount相等
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

在看一下ArrayList的remove方法。

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    private void fastRemove(int index) {
    //移除元素,modcount++
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

假设ArrayList只有两个元素,删除第一个元素的代码如下:

    public static void main(String []args) {
        List<String> list = new ArrayList<>();
        list.add("ddd");
        list.add("aaa");
        for (String string : list) {
            if (string.equals("aaa")) {
                list.remove(string);
            }
            System.out.println(string);
        }
    }

上面代码并没有抛出任何异常,关键在于迭代器遍历第一个元素后,此时cursor=1,modCount=expectedModCount=2,size=2;
删除第一个元素后,cursor=1,modCount=3,expectedModCount=2,size=1;
继续执行迭代器的hasNext()时,发现cursor==size==1,迭代器认为已经遍历结束。并没有继续执行next函数,还没有来得及执行checkForComodification()循环已经结束。

总结
在这种特殊情况下,虽然此时并没有抛出异常,但是也没有按照我们的预想,将两个元素全部遍历完,只遍历了一个元素就结束了,在删除元素时,还应当使用迭代器更加安全。