ArrayList使用不当带来的ConcurrentModificationException及其五种避免方案

1. 异常出现场景

   1. 异常场景

    异常发现于我的如下一段代码中:

List<String> arrList = new ArrayList<String>() {{
    add("maple");
    add("Maple");
    add("");
    add("boy");
    add("Boys");
}};
for (String str:arrList) {
    if (str.equals("Boys")) {
        arrList.add(str);
    }
}

       代码中我使用for-each循环arrayList数组,当数组中出现与“Boys”相等时,增加一个“Boys”。运行该段代码后的报错信息如下:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at mapleboys.DemoMain.main(DemoMain.java:17)

2. 异常出现原理

通过查看for-each编译后的class文件(如下),可以发现for-each被编译器视作iterator。

List<String> arrList = new ArrayList<String>() {{
    this.add("maple");
    this.add("Maple");
    this.add("");
    this.add("boy");
    this.add("Boys");
}};
Iterator var2 = arrList.iterator();

while(var2.hasNext()) {
    String str = (String)var2.next();
    if (str.equals("Boys")) {
    arrList.add(str);
    }
}

同时,我们注意到报错信息中ArrayList$Itr.next方法调用ArrayList$Itr.checkForComodification时报错了,查看其源代码可以发现:

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];
}
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

可以看到是modCount和expectedModCount不相等,导致报错。不去深究modCount和expectedModCount的具体含义,也可从arrayList的add方法中找到出现错误的原因。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

发现modCount增加了,但是expectedModCount没有增加导致值不相等,从而引发异常。(当然如果在遍历中进行删除操作也会引发同样的异常)。

3. 五种解决方案

对于循环中增加或删除元素引发的异常,有以下几种解决方案,其中部分方案不适合进行增加操作。

3.1 for循环

for循环没有使用iterator,因此避免了异常出现

for(int i = 0; i < 5;i++) {
    if (arrList.get(i).equals("Boys"))
    arrList.add("Boys");
}

3.2 iterator操作

出现异常的原因是采用iterator进行遍历时,但是使用了arrayList的方法进行添加,因此异常,那么使用iterator来操作,就可以避免异常出现,但是iterator没有加入add方法,但是进行删除时可以使用iterator来remove进行异常避免,代码如下:

Iterator it = arrList.iterator();
while(it.hasNext()) {
    if (it.next().equals("Boys")) {
        it.remove();
    }
}

3.3 并发容器

采用并发容器,这样的集合容器在遍历时复制原有集合的内容,在拷贝的集合上进行遍历,删除/增加操作在原集合上进行,这样iterator不会检测到修改,因此可以避免异常。代码如下:

List<String> arrList = new CopyOnWriteArrayList<String>() {{
    add("maple");
    add("Maple");
    add("");
    add("boy");
    add("Boys");
}};
for (String str:arrList) {
    if (str.equals("Boys")) {
        arrList.add(str);
    }
}

3.4 for-each循环

通过使用arrayList的方法添加/删除后,跳出循环,避免iterator遍历时进行检测。代码如下:

for(String str:arrList) {
    if (str.equals("Boys")) {
        arrList.add(str);
        break;
    }
}

3.5 流操作

通过stream的filter方法进行删除操作,代码如下:

arrList = arrList.stream().filter(str -> !str.equals("Boys")).collect(Collectors.toList());

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值