为什么在foreach循环中进行元素remove/add操作,会抛异常?

原创 2018年04月15日 20:28:48

场景

有如下代码运行:

    @Test
    public void testRemove() {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        for (String item : list) {
            // 当为1时运行正常,改成2时异常ConcurrentModificationException
            if ("2".equals(item)) {
                list.remove(item);
            }
        }

        System.out.println(PrintUtils.parintArrayList(list));
    }

异常如下:

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at NormalTest.testRemove(NormalTest.java:98)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    ...

分析

将字节码反编译

反编译结果
上图发现,我们简简单单的一句for (String item : list) { 在真实的字节码中却变成了红框中的部分,进一步分析。

跟踪源码

第一行

    public Iterator<E> iterator() {
        // ArrayList中的一个内部类
        return new Itr();
    }

在创建该对象时有这么一句赋值语句
这里写图片描述
该字段在ArrayList中标识当前对象的修改次数(包括remove和add方法),跟踪代码不难发现都有这么一行代码modCount++;

第二行
查看while条件中的源码,并不会发生异常,暂且不管

        public boolean hasNext() {
            return cursor != size;
        }

第三行
仔细查看Itr代码中的next实现

        public E next() {
            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];
        }

再看checkForComodification 方法,

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

结合上面的判断,在创建Itr对象时就将ArrayList的修改次数modCount赋值给Itr迭代器对象,如果在迭代期间ArrayList对象被操作(remove和add)了导致modCount 值修改,就会报异常ConcurrentModificationException

再分析

那么为什么当判断条件为“1”时 if ("1".equals(item)) { 就不会报错呢?
查看while条件中的源码涉及到两个字段cursorsize,做个表格来一步一步记录下值的变化

public boolean hasNext() {
    return cursor != size;
}

判断条件为if ("1".equals(item)) {

注:因为是两次add,所以导致了expectedModCountmodCount初始值为2

迭代次数 cursor size expectedModCount modCount
新创建 0 2 2 2
第一次next 1 2 2 2
执行remove操作后 1 1 2 3

以上就能看到,当remove操作后就导致了cursorsize一致,也就退出了while循环,避免了异常抛出。

判断条件为if ("2".equals(item)) {

迭代次数 cursor size expectedModCount modCount
新创建 0 2 2 2
第一次next 1 2 2 2
第二次next 2 2 2 2
执行remove操作后 2 1 2 3
while判断条件仍为true,继续执行 2 1 2 3
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

所以在next代码块中的checkForComodification就抛出了异常,以上也就完美了解释了为什么当判断条件为2时抛异常了。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/red_sheeps/article/details/79952794

JavaSE之异常

-
  • 1970年01月01日 08:00

不要在 foreach 循环里进行元素的 remove/add 操作

【强制】不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。 反例: Lis...
  • abc3224302
  • abc3224302
  • 2017-04-09 15:29:33
  • 643

高级for循环使用remove/add 问题

今天在高级for循环中用了一下remove发现报错,写了个demo测试看: List a = new ArrayList(); a.add("1"); a.add("2"); for (Stri...
  • Melod_bc
  • Melod_bc
  • 2016-12-29 17:38:29
  • 2019

foreach遍历list删除元素一定会报错?

list集合平常自以为用的非常熟了,原来只是用add()方法熟练而已,碰到remove一报错就彻底暴露了自己的短板啦。 来,给list集合删除某些元素,先上一段代码:       1)报错啦     ...
  • bimuyulaila
  • bimuyulaila
  • 2016-08-01 18:42:23
  • 11090

foreach循环中为什么不要进行remove/add操作

先来看一段代码,摘自阿里巴巴的java开发手册1 List&amp;lt;String&amp;gt; a = new ArrayList&amp;lt;String&amp;gt;(); 2 a....
  • chang_ge
  • chang_ge
  • 2018-03-25 00:14:43
  • 22

foreach循环的时候remove的问题

在foreach循环期间remove了其中一个元素,然后就报错了: Exception in thread "main" java.util.ConcurrentModificationExcepti...
  • lkforce
  • lkforce
  • 2016-10-31 11:18:50
  • 1348

不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁

不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。  正例: Iterator ...
  • suyujiezhang
  • suyujiezhang
  • 2017-12-15 16:47:29
  • 312

java foreach循环中调用remove 抛出java.util.ConcurrentModificationException

这是一个快速失败的非典型例子。 for (String str : list) { if("".equals(str)){ list.remove(str); } } 因为使用...
  • u010584063
  • u010584063
  • 2015-09-02 10:58:31
  • 735

foreach不能像iterator一样在便利过程中操作元素

import Java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public ...
  • little_newBee
  • little_newBee
  • 2017-01-12 23:32:57
  • 145

add & foreach (remove)

http://www.2cto.com/kf/201403/286536.htmlforeach 就是用来遍历读取的。其他操作最好都用for循环,对于列表而言,如果有remove,则遍历时候要对i-1...
  • lc18n
  • lc18n
  • 2016-06-16 09:40:06
  • 450
收藏助手
不良信息举报
您举报文章:为什么在foreach循环中进行元素remove/add操作,会抛异常?
举报原因:
原因补充:

(最多只允许输入30个字)