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

一、常见的集合删除元素问题:使用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会变小,这样原本的元素左边就会改变,最终循环提前退出,具体可看下面图纸解析:

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

解决方案:

方式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方法的时候,两者的值还是一样的,就不会抛出异常。

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

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

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

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

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

提供了“快速失败”机制后,如果在遍历的情况下有另外的线程来删除了元素,此时因为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。

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

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

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

上一篇:python:已有数据的列表添加列表推导式


下一篇:82. Remove Duplicates from Sorted List II