为何会发生java.util.ConcurrentModificationException?

来源:互联网 发布:tensorflow gpu配置 编辑:程序博客网 时间:2024/06/11 00:58

相信很多初学的朋友都遇到过这个异常,但是又不知道怎么回事,今天我给大家说一下,通过解读源码来解析这个异常是怎样发生的,我们先写一段代码:

public static void main(String[] args) {List<String> list = new ArrayList<String>();list.add("hello");list.add("world");list.add("!");list.add("extra");for(String str:list){System.out.println(str);list.remove(str);//主要看这里}}
这段代码就是遍历集合,然后把集合的元素移除,大家可以看到,这个方法在ArrayList类里面是有的,但是为什么遍历的时候会提示这个异常呢?先看一下异常,异常信息如下:

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(Unknown Source)at java.util.ArrayList$Itr.next(Unknown Source)at test.ConcurrentModifiedExceptionTest.main(ConcurrentModifiedExceptionTest.java:14)

注意看后面的内部类Itr,说明异常是在Itr这个类抛出来的,奇怪了,明明我们调用的是list的remove方法,为什么和Itr有关呢?其实这个就和java的语法糖有关了,先不说语法糖,我们先看一下Itr这个内部类,然后找到checkForComodification方法,可能你会发现有几个地方定义了,我们要看的是Itr类的方法,如下:

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

非常简单的一个方法,modCount不等于expectedModCount的时候就抛出这个异常了,出错的位置找到了,但是为什么出错似乎还没找到,接着看,因为我们最终需要将java文件编译成class文件,执行的时候也是执行class文件,那么也就是我们写的代码被编译成class文件的时候用到了Itr这个内部类,反编译一下这个类看一下反编译之后的代码是怎样的,可能不同的反编译器会有不同的结果,但是本质上应该是一样的,因为一般是没办法还原语法糖的(或许可以可能是我对jvm了解不够),因为编译的时候语法糖就被去掉了,除非class文件中包含还原语法糖的信息,代码如下:

public static void main(String[] args) {        ArrayList list = new ArrayList();        list.add("hello");        list.add("world");        list.add("!");        list.add("extra");        Iterator var3 = list.iterator();        while(var3.hasNext()) {            String j = (String)var3.next();            list.remove(j);        }

可以看到红色的代码就是我们的源码被编译之后的代码(当然这个是我们反编译之后的,class文件内容是字节码),也就是我们使用foreach语法之后会被编译器进行语法糖解析最终的使用的是iterator的方式进行遍历的,红色代码第一句返回的就是一个Itr类的实例:

public Iterator<E> iterator() {        return new Itr();    }
关键是最后一句,调用list的remove方法的时候里面有一句modCount++;这句改变了这个值,但是expectedModCount这个值并没有变,所以如果使用foreach的方式进行编码的时候记得要调用iterator的remove方法,否则就会抛异常。可能你写成以下这样了:

Iterator<String> it = list.iterator();while(it.hasNext()){it.remove();}

但是你发现还是会抛异常,只是异常信息不一样了,明明也是调用了iterator的remove方法啊,怎么还是异常?看了源码会发现remove方法的第一句就是判断lastRet是否小于0,初始化就是-1,所以肯定会抛异常,那么应该怎么做?很简单,就调用一下next()方法就ok了。当然一般情况下我们都会调用next方法的,这里只是说明一下。














阅读全文
0 0
原创粉丝点击