专栏首页猿小俊执行ArrayList的remove(object)方法抛异常?

执行ArrayList的remove(object)方法抛异常?

简介

或许有很多小伙伴都尝试过如下的代码:

ArrayList<Object> list = ...;
for (Object object : list) {
    if (条件成立) {
        list.remove(object);
    }
}

然后会发现抛出java.util.ConcurrentModificationException异常,这是一个并发异常。那么这个到底是什么情况?首先需要介绍一下增强for循环

增强for循环

增强for循环是Java1.5后,Collection实现了Iterator接口后出现的。增强for循环的代码如下

for (Object object : list) {
    // 操作
}

其实增强for循环就是使用Iterator迭代器进行迭代的,增强for循环就变成下面这样:

Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()) {
    iterator.next();
    // 操作
}

那么为什么在增强for循环中调用list.remove(object)会出事呢?那么咱们看看ArrayList下的 Iterator的实现类: Itr类

Itr子类

Itr子类是Iterator的实现类,属于ArrayList私有的局部内部类。我截取了Itr类的部分代码,如下:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int expectedModCount = modCount;

    Itr() {}

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

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        ...
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

elementData 是ArrayList存放元素的数组,上面代码没有贴出来。 size 是elementData实际存放的容量大小 modCount 记录elementData容量的修改次数 expectedModCount 记录实例化迭代器Itr时,elementData容量的修改次数 注意!:在迭代器中,当执行next方法的时候,会去调用checkForComodification方法,判断elementData 的容量是否被修改过。 然后来看看ArrayList的remove(object)方法,截取部分代码如下:

public boolean remove(Object o) {
    for (int index = 0; index < size; index++)
	    if (找到目标元素) {
	        fastRemove(index);
	        return true;
	    }
	return false;
}

private void fastRemove(int index) {
    modCount++;
    // 移除操作
}

可以发现,调用remove(object)方法时调用了fastRemove方法,在fastRemove方法中执行modCount++ ! 现在把文章开头的代码拷下来,再来分析一次:

ArrayList<Object> list = ...;
for (Object object : list) {
    if (条件成立) {
        list.remove(object);
    }
}

当执行了list.remove时,执行modCount++ 。此时迭代器再往下进行迭代,执行了next方法,发现 modCount != expectedModCount,那么则抛出java.util.ConcurrentModificationException异常。 之所以Iterator认为是一个并发异常。是因为你不在迭代器里操作,而是在迭代器外面进行remove操作呀! 难道没有其他解决方案吗?有滴。

解决方案

那么就是使用Itr的 remove方法。Itr子类重写了 remove 方法,这里部分代码:

public void remove() {
    ...
    try {
        ArrayList.this.remove(需要删除元素的索引);
        ...
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

其实很简单,就是remove后,把 expectedModCount 同步一下 modCount 的值,这就解决了。完整代码如下:

ArrayList<Object> list = ...;
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()) {
    iterator.next();
    iterator.remove();
}

总结

本来我还不知道增强for循环是调用Iterator进行迭代的,要不是我debug了一波,我还不知道呐。还是小有收货。

个人博客网址: https://colablog.cn/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringBoot Web篇(二)

    当我们服务器需要接收用户上传的文件时,就需要使用MultipartFile作为参数接收文件。如下:

    Johnson木木
  • 如何在Nginx不绑定域名下使用SSL/TLS证书?

    该文主要记录如何在没有购买域名的情况下使用SSL/TLS协议,即地址前面的http变成了https。但是这样的SSL协议是会被浏览器认为是不安全的。在开发或者测...

    Johnson木木
  • SpringBoot 构建 REST 服务

    启动项目后浏览器打开http://localhost:8080/,可以看到如下信息:

    Johnson木木
  • Python的列表学习(四)

    列表的定义很简单,关键字是list,比如我们定义一个列表,它的所有的方法来自list类,我们可以来看下llist类的方法,见如下的代码:

    无涯WuYa
  • Django的路由控制

    URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于...

    py3study
  • FFmpeg封装格式处理2-解复用例程

    本例实现,将输入文件中的视频流和音频流分离出来,保存为单独的文件,所保存的文件是不含封装格式的裸流文件。

    用户4940323
  • EF Core 基础知识

    EF Core 默认会与 ASP.NET Core的日志提供程序一起工作,只需要使用AddDbContext或AddDbContextPool添加服务即可。

    thz
  • Js基础教程之for循环语句

    老雷PHP全栈开发
  • 基于 Composer 的 PHP 模块化开发

    overtrue
  • How does CRM middleware knows which sites are interested with CRM local changes

    You could see the following BDOC in CRM which means the BDOC will be sent to mul...

    Jerry Wang

扫码关注云+社区

领取腾讯云代金券