Java中集合删除元素时候关于ConcurrentModificationException的迷惑点

Posted 进无止境

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中集合删除元素时候关于ConcurrentModificationException的迷惑点相关的知识,希望对你有一定的参考价值。

下面的示例来至于阿里巴巴Java开发手册的集合处理部分的第7条:

运行如下代码,会发现正确运行。

    public static void hasNotExcption() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");

        for (String item : list1) {
            System.out.println("item : "+item);
            if ("1".equals(item)) {
                list1.remove(item);
            }
        }

    }

但是运行如下代码,则异常:java.util.ConcurrentModificationException(和1中的代码区别是上面移除判断条件是1,下面的判断条件是2)

    public static void hasExcption() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");

        for (String item : list1) {
            System.out.println("item : "+item);
            if ("2".equals(item)) { 
                list1.remove(item);
            }
        }

    }

 

再看如下代码示例,运行结果见注释:

    /**
     *没有异常
     */
    public static void hasNotExcption() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");
        list1.add("3");
        list1.add("4");

        for (String item : list1) {

            System.out.println("item : " + item);
            // 移除1和3时候会抛ConcurrentModificationException异常
            // 但是移除2的时候不会抛出异常
            if ("3".equals(item)) {
                list1.remove(item);
            }
        }

    }

    /**
     * 有异常
     */
    public static void hasExcption1() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");
        list1.add("3");

        for (String item : list1) {// 增强的for循环底层实现使用的是迭代器
            if ("1".equals(item)) {
                // 移除并修改“modCount变量”,导致下次遍历时候异常
                list1.remove(item);
            }
        }

    }

    /**
     * 有异常
     */
    public static void hasExcption2() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");
        list1.add("3");

        for (String item : list1) {
            if ("3".equals(item)) {
                list1.remove(item);
            }
        }

    }

通过上面三个例子是不是发现了结论?只要移除的是倒数第二个元素的话,就不会发生异常!的确是,移除倒数第二个元素的话就不会异常,那究竟是为什么呢?

 

知识点1:关于增强for循环的实现

集合的增强for循环实现内部使用的是迭代器,可以通过eclipse的F5调试跟踪。

        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");
        list1.add("3");
        for (String item : list1) {
            if ("3".equals(item)) {
                list1.remove(item);
            }
        }

 

知识点2:集合如果判定的是并发修改错误?

细心的话可以通过异常发现异常的判断代码是:

if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

其中的modCount是记录集合的修改次数,而expectedModCount是记录的预期的修改次数,当集合的修改次数和预期修改次数不一致的时候则发生异常。

那么什么时候会修改modCount呢?当我们对集合进行删除或者添加元素时候此时记录集合的修改次数就会发生增加,但是期望的修改次数不会变化,当检查判断异常条件时候就会根据条件处理。

 

到此回归到上面问题,对于hasExcption1和hasExcption2是如何发生异常的?本例对hasExcption1进行详细解释:

    /**
     * 有异常
     */
    public static void hasExcption1() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");
        list1.add("3");
        // 增强的for循环底层实现使用的是迭代器。所以每一次都是调用一个hasNext,然后在调用next方法返回元素给item
        for (String item : list1) {
            
            if ("1".equals(item)) {
                // 移除并修改“modCount变量”,导致下次遍历时候异常
                list1.remove(item);
            }
        }

    }

增强的for循环底层实现使用的是迭代器。所以每一次都是调用一个hasNext,然后在调用next方法返回元素给item。当我们删除元素之后,下一次循环时候会先调用hasNext是否还有元素,此时还有。在调用next方法返回该元素给item,但是调用next方法时候会检测

集合修改次数和预期修改次数是否相等,如果不等的话则抛出异常。(本例是不等的,因为移除1之后modCount=4,而expectModCount=3)。

 

那么为什么删除倒数第二个元素不会异常呢?示例代码:

/**
     *没有异常
     */
    public static void hasNotExcption() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");
        list1.add("3");
        list1.add("4");

        for (String item : list1) {
            if ("3".equals(item)) {
                list1.remove(item);
            }
        }

    }

因为我们删除倒数第二个元素时候,此时size=size-1,当删除完元素进入下一次循环时候,此时hasNext方法判断是否还有元素的时候返回是false(hasNext使用的是游标和size进行比较)。所以就不会调用next方法了。上面例子我们知道异常就是在next方法中抛的,所以

删除倒数第二个元素就不会有异常。

 

注意:如果删除的是倒数第二个元素那么最后的结果是否正确呢?答案:显然是不正确的。

通过如下示例代码检验:

    /**
     *没有异常
     */
    public static void hasNotExcption() {
        List<String> list1 = new ArrayList<String>();
        list1.add("1");
        list1.add("2");
        list1.add("3");
        list1.add("3");

        for (String item : list1) {
            if ("3".equals(item)) {
                list1.remove(item);
            }
        }
        // 显然最后一个3没有删除,所以结果错误
        System.out.println(list1); //[1, 2, 3]

    }

结果是错误的,原因:因为最后一个元素由于在移除倒数第二个元素时候将size-1了,所以遍历集合提前结束了。所以没有完全移除掉所有的“3”

 

以上是关于Java中集合删除元素时候关于ConcurrentModificationException的迷惑点的主要内容,如果未能解决你的问题,请参考以下文章

在java中集合List,Set,Map,Properties的区别?

python中集合的用法

Java——关于Java中集合的面试题

Java——12个关于Java中集合的面试题

Java中集合概念

Java中集合(List,Set,Map)