日常开发中常遇到的一些问题和思考

举报
IT学习日记v 发表于 2022/01/19 23:46:50 2022/01/19
【摘要】 JAVA中开发中最常见的for循环删除元素问题以及迭代器相关知识点,Integer缓存常见异常问题归类和总结

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。


  • 🤟 博主介绍: CSDN、头条、知乎等平台优质博主,全网粉丝2w+
  • 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦

一、常见的集合删除元素问题:使用for循环匹配某个元素,然后删除,猜猜下面代码输出的会是什么?

List<String> list = new ArrayList<>();
    list.add("淘宝");
    list.add("淘宝");
    list.add("掘金");
    for(String item : list) {
        if("淘宝".equals(item)) {
            list.remove(item);
        }
    }
System.out.println(list);

解答: 输出【淘宝、掘金】,本意我们是想删除集合中为“淘宝”的元素,但是因为在循环中使用到list的remove方法,这样一旦有元素被移除后,集合的size会变小,这样原本的元素左边就会改变,最终循环提前退出,具体可看下面图纸解析:

image-20220119231352409

解决方案:

方式1: 在调用remove方法后添加:i–,让下一轮坐标重新回到删除元素的前一个(因为这样删除会让被删除元素之后的元素左边都往前移一位)

方式2: 使用java8提供的removeIf API,完整代码:list.removeIf(e -> “淘宝”.equals(e)) 即可实现。

二、使用增强for循环删除元素,猜猜下面代码会输出什么?

List<String> list = new ArrayList<>();
    list.add("淘宝");
    list.add("淘宝");
    list.add("掘金");
    for(String item : list) {
        if("淘宝".equals(item)) {
            list.remove(item);
        }
    }
System.out.println(list);

解答: 直接抛出ConcurrentModificationException异常,其实增强for循环只是一个语法糖,方便开发者使用,在虚拟机实际的被执行的时候是通过迭代器的方式循环,对上面的代码进行反编译得到如下代码:

List<String> list = new ArrayList();
    list.add("淘宝");
    list.add("淘宝");
    list.add("掘金");
    Iterator iterator = list.iterator()
    while(iterator .hasNext()) {
        String item = (String)iterator .next();
        if ("淘宝".equals(item)) {
            list.remove(item);
        }
    }
    System.out.println(list);

为什么使用迭代器循环方式在循环的时候删除元素会抛出这个异常呢? 实际上,迭代器在每次调用next()方法的时候,第一步就会先去执行checkForComodification()方法,这个方法的目的就是检查list下的modCount是否 和expectedModCount变量的值一致(可以理解为版本号),如果不一致,则直接抛出ConcurrentModificationException异常。

删除的流程是这样的呢? 实际上modCount变量表示的含义是集合被修改的次数,每次对集合进行add或者remove的时候,则会对它进行+1,在调用集合的remove方法时,该变量的值会被加1,但是expectedModCount的值却没有变动,此时,当再次执行next方法时判断则两者值不相等,于是抛出异常。

解决方案:使用迭代器的remove方法,即将上面删除的代码修改为: iterator.remove(), 在迭代器的remove方法中,会将modCount的值赋值给expectedModCount,所以在下次执行next方法的时候,两者的值还是一样的,就不会抛出异常。

image-20220119231552752

更优雅的解决方案: 使用java8中提供的removeIf方法,如:list.removeIf(e -> “淘宝”.equals(e)) ,其实这个方法内部也是使用了迭代器的方式进行删除。

了解完解决方案,我们再来深入研究下抛出异常的目的是为什么呢?

其实它是集合的一种保护机制,叫做“快速失败”,因为集合的remove操作都是非原子性的,在多线程情况下,可能出现一个线程在遍历的时候另外一个线程执行了删除操作,当集合的元素被删除后,集合的容量就会变小,在被删除的元素后面的元素坐标都会往前移动一位,这样循环迭代的时候就可能出现漏掉某些元素的情况,这种数据不同可能在当前场景下不会出现异常情况,但是会在某些代码环节出现异常情况,增加了排查的难度。

image-20220119231608357

提供了“快速失败”机制后,如果在遍历的情况下有另外的线程来删除了元素,此时因为modCount和expectedModCount值不一致,则会抛出异常,迭代停止,这样我们就能够快速定位异常原因和位置。

三:Integer类型比较,猜猜下面返回什么结果

public static void test3(){
    Integer a = 127;
    Integer b = 127;
    Integer c = 128;
    Integer d = 128;
    
    System.out.println(a == b);
    System.out.println(c == d);
    System.out.println(c.equals(d));
}

解答: 答案是true、false、true,你猜对了?

为什么会出现这样的结果呢,因为是Integer缓存了-128到127的数值,当使用"=="比较符时,实际上比较的是两个对象的地址,因为Integer将-128到127的数值都进行了缓存,所以在这个范围内的相同的两个值无论是使用==还是equals比较结果都是true,因为它们是直接取的缓存中的值,但是不在这个范围内的话,则返回的是false。

image-20220119231621199

当使用的是equals方法比较时,不在这个范围内的相同的两个值返回的结果也是true,因为Integer内部重写了equals方法,该方法比较的是两个对象的中的值而不是地址。

建议:使用包装类比较值时,不要使用"==",而应该使用equals方法。


  如果文章有帮助,请给作者关注、点赞、收藏、评论,让博主有动力创作更加优质的文章。

白色简约动画引导关注.gif

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。