为什么foreach中不能用集合类的add 、remove方法?

之前没有关注过这个,直到有天类似这样的代码上线:

    public class ForEachTest {
    public static void main(String[] args){
        List<String> test = new ArrayList<String>();
        test.add("a");
        test.add("b");
        test.add("c");

        test.forEach(a ->{
            if(a.equals("a")){
                test.remove(a);
            }
        });
    }
}

导致出现如下异常:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList.forEach(ArrayList.java:1258)
	at ForEachTest.main(ForEachTest.java:14)

为什么会报错呢?让我们通过源码一探究竟。

由于我的反编译工具,没法反编译lamda表达式,所以我把上述foreach代码调整为:

for(String a:test){
            if(a.equals("a")){
                test.remove(a);
            }
        }

异常堆栈为:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
	at java.util.ArrayList$Itr.next(ArrayList.java:857)
	at ForEachTest.main(ForEachTest.java:14)

结果反编译出来和我代码是一样的。不知道别人文章里面是怎么反编译出来的。

直接把别人反编译的结果拿来:

List a = new ArrayList();
a.add("1");
a.add("2");
Iterator i$ = a.iterator();
do
{
    if(!i$.hasNext())
        break;
    String temp = (String)i$.next();
    if("1".equals(temp))
        a.remove(temp);
} while(true);

看异常堆栈,是在Itr类的next()方法中的checkForComodification抛出的。Itr是ArrayList的一个内部类,其实现了Iterator接口。看一下抛异常的这个方法:

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

确认是这里抛出的异常了。那么我们倒要看看,modCount和expectedModCount到底是什么意思。

expectedModCount是Iter的属性:

int expectedModCount = modCount;

可见,在开始迭代的时候,expectedModCount的值和modCount是相等的。而在迭代的过程中,出现了两者不一致的情况,导致checkForComodification方法抛出了异常。

在寻找哪里修改导致两者出现不一致的情况前,我们先看下modCount的含义。

modCount继承自AbstractList,初始值为0:

protected transient int modCount = 0;

看下remove方法方法中,对modCount和expectedModCount做了什么,注意,这里执行的是ArrayList的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;
    }

看下fastRemove方法:

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work
    }

对modCount进行了+1操作。所以在for循环执行到第二次时,modCount就比expectedCount大1了。

之所以出现这个问题,还是因为expectedCount是Itr的变量,而modCount是ArrayList的,当用ArrayList的remove(object)、add(object)方法时,没有同步更新expectedCount。看一下Itr的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();
            }
        }

可以看到,在remove之后,有赋值expectedModCount 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值