zoukankan      html  css  js  c++  java
  • 简单说明:ArrayList 在 For 循环中进行删除而产生异常的原因

    经常会有人这么对 list 进行遍历,错而不自知。

    示例代码如下:

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

    以上代码执行导致的报错信息如下:

    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 demo.service.impl.test.main(test.java:14)
    

    网上有很多博客对此都做了说明,这篇文章通过比较浅显易懂的方式说明报错产生的原因。

    一、list.add

    list.add 代码执行时,有一个变量发生改变了,那就是 modCount。在代码中 list.add 共执行4次,所以 modCount 的值为 4。

    注:listadd()remove()clear() 都会改变 modCount 值。

    二、for (String str : list)

    for (String str : list) 调用的是 ArrayList 中内部类 ItrItr 是对 Iterator 的实现。而在 Iterator 开始前,会先执行 int expectedModCount = modCount

    此时 expectedModCountmodCount 均为 4

    三、list.remove(“aaa”)

    在此处先看一下会报错的原因,以下是源码:

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

    modCountexpectedModCount 不相等了,所以报错。

    有人可能会跟我有一样的想法,为什么 list.remove(“aaa”) 时,不把 expectedModCount = modCount 重新赋值一次。其实是有的,只是调用的方法错了。

    例子中 list.remove(“aaa”) 调用的 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
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    

    而使 modCount 的值改变的是其中的 fastRemove 方法。

    fastRemove 源码如下:

    private void fastRemove(int index) {
        // 此处 modCount + 1
        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
    }
    

    而真正使 expectedModCount = modCount 执行的源码如下:

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
    
        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }
    

    此代码在内部类 Itr 中。

    这也就是为什么会说,如果 list 在循环中有删除操作,最好用 iterator 迭代的方式去做。

    四、总结

    简单总结一下

    • list.remove() 没有对 expectedModCount 重新赋值
    • iterator.remove()expectedModCount 重新赋值

    建议大家跟踪一下源代码,代码量不多,也很容易理解。

    附录:内部类 Itr 源码(不长,分分钟看完)

    private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;
    
        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;
    
        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;
    
        public boolean hasNext() {
            return cursor != size();
        }
    
        public E next() {
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
    
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
    
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    
  • 相关阅读:
    vue-router2.0 组件之间传参及获取动态参数
    vuex
    移动端微信部分知识总结
    移动端js知识总结
    [luogu]P4365[九省联考]秘密袭击coat(非官方正解)
    [luogu]P4364 [九省联考2018]IIIDX
    [luogu]P4363 [九省联考2018]一双木棋chess
    后缀数组自用
    BZOJ5288 [Hnoi2018]游戏
    Bzoj5290: [Hnoi2018]道路
  • 原文地址:https://www.cnblogs.com/zhenggc/p/13655490.html
Copyright © 2011-2022 走看看