千家信息网

Java中ArrayList陷阱实例分析

发表于:2025-01-21 作者:千家信息网编辑
千家信息网最后更新 2025年01月21日,这篇文章主要介绍"Java中ArrayList陷阱实例分析"的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇"Java中ArrayList陷阱实例分析"文章能帮助大
千家信息网最后更新 2025年01月21日Java中ArrayList陷阱实例分析

这篇文章主要介绍"Java中ArrayList陷阱实例分析"的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇"Java中ArrayList陷阱实例分析"文章能帮助大家解决问题。

    问题分析

    疑惑满满

    小枫听到这个面试题的时候,心想这是什么水面试官,怎么问这么简单的题目,心想一个for循环加上equal判断再删除不就完事了吗?但是转念一想,不对,这里面肯定有陷阱,不然不会问这么看似简单的问题。小枫突然想起来之前写代码的时候好像遇到过这个问题,也是在ArrayList中删除指定元素,但是直接for循环remove元素的时候还抛出了异常,面试官的陷阱估计在这里。小枫暗自窃喜,找到了面试官埋下的陷阱。 小枫回想起当天的的测试情况,代码进行了脱敏改造。当初是要在ArrayList中删除指定元素,小枫三下五除二,酣畅淋漓的写下了如下的代码,信心满满的点了Run代码的按钮,结果尴尬了,抛异常了。

    public class TestListMain {    public static void main(String[] args) {        List result = new ArrayList<>();        result.add("a");        result.add("b");        result.add("c");        result.add("d");        for (String s : result) {            if ("b".equals(s)) {                result.remove("b");            }        }    }}

    一个大大红色的异常马上就出来了,OMG,怎么会这样呢,感觉代码没什么问题啊,赶紧看看抛了什么异常,在哪里抛的异常吧。可以看出来抛了一个ConcurrentModificationException的异常,而且是在Itr这个类中的一个检测方法中抛出来的异常,这是怎么回事呢?我们的原始代码中并没有这个Itr代码,真是百思不得其解。

    拨云见日

    既然从源代码分析不出来,我们就看下源代码编译后的class文件中的内容是怎样的吧,毕竟class文件才是JVM真正执行的代码,不看不知道,一看吓一跳,JDK原来是这么玩的。原来如此,我们原始代码中的for-each语句,编译后的实际是以迭代器来代替执行的。

    public class TestListMain {    public TestListMain() {    }    public static void main(String[] args) {        List result = new ArrayList();        result.add("a");        result.add("b");        result.add("c");        result.add("d");        //创建迭代器        Iterator var2 = result.iterator();        while(var2.hasNext()) {            String s = (String)var2.next();            if ("b".equals(s)) {                result.remove("b");            }        }    }}

    通过ArrayList创建的Itr这个内部类迭代器,于是for-each循环就转化成了迭代器加while循环的方式,原来看上去的for-each循环被挂羊头卖狗肉了。

      public Iterator iterator() {        return new Itr();    }

    Itr这个内部类迭代器,通过判断hasNext()来判断迭代器是否有内容,而next()方法则获取迭代器中的内容。

     private class Itr implements Iterator {        int cursor;       // index of next element to return        int lastRet = -1; // index of last element returned; -1 if no such        int expectedModCount = modCount;        Itr() {}        public boolean hasNext() {            return cursor != size;        }        @SuppressWarnings("unchecked")        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];        }     ...      }

    大致的过程如下所示:

    真正抛异常的地方是这个检测方法, 当modCount与expectedModCount不相等的时候直接抛出异常了。那我们要看下modCount以及expectedModCount分别是什么。这里的modCount代表ArrayList的修改次数,而expectedModCount代表的是迭代器的修改次数,在创建Itr迭代器的时候,将modCount赋值给了expectedModCount,因此在本例中一开始modCount和expectedModCount都是4(添加了四次String元素)。但是在获取到b元素之后,ArrayList进行了remove操作,因此modCount就累加为5了。因此在进行检查的时候就出现了不一致,最终导致了异常的产生。到此我们找到了抛异常的原因,循环使用迭代器进行循环,但是操作元素却是使用的ArrayList操作,因此迭代器在循环的时候发现元素被修改了所以抛出异常。

    我们再来思考下,为什么要有这个检测呢?这个异常到底起到什么作用呢?我们先来开下ConcurrentModificationException的注释是怎么描述的。简单理解就是不允许一个线程在修改集合,另一个线程在集合基础之上进行迭代。一旦检测到了这种情况就会通过fast-fail机制,抛出异常,防止后面的不可知状况。

    /** *** * For example, it is not generally permissible for one thread to modify a Collection * while another thread is iterating over it.  In general, the results of the * iteration are undefined under these circumstances.  Some Iterator * implementations (including those of all the general purpose collection implementations * provided by the JRE) may choose to throw this exception if this behavior is * detected.  Iterators that do this are known as fail-fast iterators, * as they fail quickly and cleanly, rather that risking arbitrary, * non-deterministic behavior at an undetermined time in the future. *****/public class ConcurrentModificationException extends RuntimeException {    ...}

    如何正确的删除

    既然抛异常的原因是循环使用了迭代器,而删除使用ArrayList导致检测不通过。那么我们就循环使用迭代器,删除也是用迭代器,这样就可以保证一致了。

    public class TestListMain {    public static void main(String[] args) {        List result = new ArrayList<>();        result.add("a");        result.add("b");        result.add("c");        result.add("d");       Iterator iterator = list.iterator();                 while (iterator .hasNext()) {                        String str = iterator.next();                        if ("b".equals(str)) {                                iterator.remove();                        }    }}

    关于"Java中ArrayList陷阱实例分析"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注行业资讯频道,小编每天都会为大家更新不同的知识点。

    迭代 循环 代码 元素 时候 陷阱 分析 问题 检测 内容 方法 实例 实例分析 是在 知识 原始 一致 代表 原因 实际 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 宿州阿拉丁软件开发 删除服务器上的文件夹 定制化服务器生产厂商 电信托管服务器 部署web应用服务器配置 网络安全和国家安全之间的关系 云上城之歌不同服务器能一起玩吗 网络技术汇流排是什么 软件开发项目成本结构 长岛游戏软件开发公司 女生做软件开发怎么样 海南省ipfs云服务器云空间 中国政企网络安全服务上岗证 全球无线通信网络技术 杭州吉小满互联网科技有限公司 分布式分析型数据库国内外差距 日本人软件开发使用excel 网络安全深度防护研究与应用 系统登录为什么要用数据库 中职学生网络安全黑板报 服务器管理与配置试题 以色列网络安全技术培训 凹凸世界网络安全手抄报 2020网络技术大赛 长沙手机软件开发收费多少 数据库有的能连上有的连不上 使用境外服务器连接数据库 dhcp服务器实验报告 一个关系数据库由若干个 体验戴尔服务器
    0