List foreach 抛出java.util.ConcurrentModificationException分析

前言

这篇文章我们讨论2个问题:

1、List集合通过foreach方式遍历的时候调用remove方法跑出异常的原因

2、List集合中存在相同的数据时,调用revove方法删除数据时的结果分析

正文

foreach遍历中调用remove方法抛异常

先看下面的代码

 List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");   
        for (String str: list){
            System.out.println(str);
            list.remove(str);
        }

运行后的结果抛出异常

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
    at java.util.ArrayList$Itr.next(ArrayList.java:861)

通过debug的运行,我们通过跟踪代码发现,在第一次revove方法被调用的时候,是正常的,当第二次revove的时候会跑出异常,通过异常信息可以看出,是调用了迭代器中的checkForComodification,校验迭代器中的expectedModCount 与modCount不相等,从而抛出异常,那么会有一个问题,foreach的方式,为啥会调用迭代器呢,我们带着这个问题,查看下编译后的代码,如下图所示

 List<String> list = new ArrayList();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        Iterator var4 = list.iterator();
        while(var4.hasNext()) {
            String str = (String)var4.next();
            System.out.println(str);
            list.remove(str);
        }

通过上面编译文件发现,jvm将foreanch的方式,编译成迭代器的方式,来遍历,而 expectedModCount变量是迭代器中的,expectedModCount变量值在初始化的时候是等于modCount,而我们调用list.remove方法的时候,remove方法只会修改modCount的数量,不会修改迭代器中的expectedModCount值,所以在校验的时候,会导致2着不一致。

那么如果结果这个问题??

我们如果想在遍历中删除,可以采用迭代器遍历,调用迭代器中的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();
            }
        }

 从源码中可以看出先调用List的remove方法,删除元素,然后将modCount值再次赋值给expectedModCount,从而在调用revove进行比较的时候,二者的值就是相同的。

List集合存在相同数据,调用remove删除的结果分析

 先举个例子,如下

 List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("A");
        list.remove("A");
        for (int i = 0;i<list.size();i++){
            System.out.println(list.get(i));
        }

在list中存在2个相同的A元素,那么我们在调用revove方法的时候,结果是2个A都被删除,还是说只会删除一个,如果是删除一个,那么是删除第一个还是第二个呢?

有句话叫实践出真理,在这里叫源码出真理,我们看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;
    }

因为我们入参不是null,所以做else逻辑,逻辑中从下标0开始遍历,匹配元素是否等于o,如果是,那么调用 fastRemove(index)方法返回。

所以从源码中看出,当匹配到一个元素后不会再匹配下一个,所以只会删除一个,而且是第一个元素,我们检验是否和分析的结果一致

B
C
D
A

没问题

猜你喜欢

转载自blog.csdn.net/zanpengfei/article/details/124706169