【小家java】Java中集合List、Set、Map删除元素的方法大总结(避免ConcurrentModificationException异常)(下)

简介: 【小家java】Java中集合List、Set、Map删除元素的方法大总结(避免ConcurrentModificationException异常)(下)

要了解这个为什么报错,我们需要知道两点:


1、增强for循环到额原理是什么?


为了给大家解释清楚这个问题,我特意找到了.class文件,让大家看看增强for的真身:


image.png


看到编译后的代码,我们发现底层还是有迭代器实现的,并且,并且,并且,你会发现它调用的是list的remove方法,但是这却不是报错的根源,咱们得继续看下面的源码分析


2、fast-failed机制什么时候会触发?


从报错信息中看,remote方法没有报错,报错的是next方法,因此我贴出源码


这里面有快速失败的检查,所以最终结论是,我们也不能用增强for删除。那怎么办呢?


最后介绍一种正确的方法(推荐):

//3、迭代器iterator遍历
Integer baseNum = 5; //以这个为基础 删除掉>=这个值的元素
System.out.println("剩余长度:" + list.size() + "---" + list);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    Integer x = it.next();
    if (x >= baseNum) {
        it.remove();
        //list.remove(x); //java.util.ConcurrentModificationException
    }
}
System.out.println("剩余长度:" + list.size() + "---" + list);
输出结果为:
剩余长度:9---[2, 1, 3, 5, 8, 6, 2, 5, 9]
剩余长度:4---[2, 1, 3, 2]

bingo,终于拿到了我们想要的结果了。但此处一定要注意,一定要注意,一定要注意。重说三,我们remove的时候,一定只能使用迭代器的remove方法,否则也还是会报错的,重点一定要注意。


最后,我再介绍两种也能同样达到效果的删除方法,但是并不推荐这么去做,知道就行了


方法一:使用CopyOnWriteArrayList 支持并发修改的List来代替,缺点就是效率低


方法二:采用对List倒序遍历的方式。缺点是:理解难道相较于迭代器偏大,需要结合List内部实现才能真正理解。当然因为size需要实时计算,速度上也不占优势

Integer baseNum = 5;
 System.out.println("剩余长度:" + list.size() + "---" + list);
 for (int i = list.size() - 1; i >= 0; i--) {
     //for (int i = 0; i < list.size(); i++) {
     if (list.get(i) >= baseNum)
         list.remove(i);
 }
 System.out.println("剩余长度:" + list.size() + "---" + list);

这里再说说关于Map的删除情况,怎么删除才能不抱错呢?


image.png


这样子,采用map的remove方法,也报错:


public static void main(String[] args) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(1, 1);
        map.put(2, 2);
        map.put(5, 5);
        map.put(3, 3);
        map.put(10, 10);
        Iterator<Integer> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Integer key = iterator.next();
            if (key >= 5) {
                map.remove(key);
            }
        }
        System.out.println(map);
    }
报错:Exception in thread "main" java.util.ConcurrentModificationException


由此课件,map也有类似情况,因此此处举一例,map可以安全删除的例子


代码同上,只需要调用iterator.remove();即可,不要用map.remove(key);

3、使用场景


一句话:集合的使用场景有哪些,这个就有哪些。所以显然:everywhere!


小彩蛋


Map中的putIfAbsent、computeIfAbsent、computeIfPresent、compute

put方法参考:

 

public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        System.out.println(map.put("1", 2)); //null 如果是添加成功,返回null
        System.out.println(map.put("1", 3)); //2  如果已经存在此key了 会用新值覆盖旧值  然后把oldValue返回出来
        System.out.println(map); //{1=3}
    }

putIfAbsent


如果给定的key不存在(或者key对应的value为null),就把value放进去,并返回null;

如果存在,返回当前值(不会把value放进去);

public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        System.out.println(map.putIfAbsent("1", 2)); //null 如果是添加成功,返回null
        System.out.println(map.putIfAbsent("1", 3)); //2  如果已经存在此key了,不会覆盖此value值。  返回的还是oldValue 
        System.out.println(map); //{1=2}
    }

computeIfAbsent(重要)


如果给定的key不存在(或者key对应的value为null),就去计算mappingFunction的值(计算结果决定了返回值有两种情况如下,我们姑且叫计算的结果为newValue):


若key已经存在,就不会执行mappingFunction。返回oldValue


1.newValue == null,不会替换旧值。返回null


2.newValue != null,替换旧值。返回新值

 

public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        System.out.println(map.computeIfAbsent("1", key -> Integer.valueOf(key) + 2)); //3 计算出来的newValue放进去,并且返回newValue
        System.out.println(map.computeIfAbsent("1", key -> Integer.valueOf(key) + 3)); //3 key已经存在了,不作为
        System.out.println(map.computeIfAbsent("2", key -> null)); //3 key虽然不存在,但计算出为null,所以不作为,并且返回null
        System.out.println(map); //{1=3}
    }

computeIfPresent


和上面相反,当key存在且对应的oldValue不为null时,执行计算,否则啥都不做。


public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        System.out.println(map.computeIfPresent("1", (k, v) -> Integer.valueOf(k) + v)); //null key不存在,不作为  且返回null
        System.out.println(map.computeIfPresent("1", (k, v) -> Integer.valueOf(k) + v)); //null  key还是不存在,不作为 且返回null
        map.put("1", 1);
        System.out.println(map.computeIfPresent("1", (k, v) -> Integer.valueOf(k) + v)); //2  key存在,开始计算。把计算的结果覆盖掉,然后返回计算的结果
        System.out.println(map); //{1=2}
        System.out.println(map.computeIfPresent("1", (k, v) -> null)); //null key存在,开始计算 但是计算结果为null。所以返回null,此处注意remove()掉了对应的key
        System.out.println(map); //{}
    }

典型应用场景(以computeIfAbsent为例)


    public static void main(String[] args) {
        Map<String, List<String>> map = new HashMap<>();
        List<String> list;
        // 以前一般这样写,各种判断==========================================================
        list = map.get("list-1");
        if (list == null) {
            list = new LinkedList<>();
            map.put("list-1", list);
        }
        list.add("one");
        //==========================================================
        // 使用 computeIfAbsent 可以这样写 这样若已存在,返回值oldValue,若不存在就执行计算,也是返回一个可用的List 
        // 这样对null非常友好,特别好用~~~~~~~~~~
        list = map.computeIfAbsent("list-1", k -> new ArrayList<>());
        list.add("one");
    }

4、最后

集合作为我们使用最为广泛的数据结构,因此了解这里面的一些原理和正确做法,能够使得我们少采坑,希望此文能够帮助到你


image.png


相关文章
|
10月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
560 18
|
10月前
|
存储 Java 索引
(Python基础)新时代语言!一起学习Python吧!(二):字符编码由来;Python字符串、字符串格式化;list集合和tuple元组区别
字符编码 我们要清楚,计算机最开始的表达都是由二进制而来 我们要想通过二进制来表示我们熟知的字符看看以下的变化 例如: 1 的二进制编码为 0000 0001 我们通过A这个字符,让其在计算机内部存储(现如今,A 字符在地址通常表示为65) 现在拿A举例: 在计算机内部 A字符,它本身表示为 65这个数,在计算机底层会转为二进制码 也意味着A字符在底层表示为 1000001 通过这样的字符表示进行转换,逐步发展为拥有127个字符的编码存储到计算机中,这个编码表也被称为ASCII编码。 但随时代变迁,ASCII编码逐渐暴露短板,全球有上百种语言,光是ASCII编码并不能够满足需求
387 4
|
11月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
290 11
|
10月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
390 4
|
10月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
511 5
|
10月前
|
存储 JavaScript Java
(Python基础)新时代语言!一起学习Python吧!(四):dict字典和set类型;切片类型、列表生成式;map和reduce迭代器;filter过滤函数、sorted排序函数;lambda函数
dict字典 Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。 我们可以通过声明JS对象一样的方式声明dict
507 2
|
存储 缓存 JavaScript
Set和Map有什么区别?
Set和Map有什么区别?
743 1
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
421 2
|
存储 JavaScript 前端开发
for...of循环在遍历Set和Map时的注意事项有哪些?
for...of循环在遍历Set和Map时的注意事项有哪些?
840 156
|
10月前
|
存储 算法 容器
set_map的实现+set/map加持秒杀高频算法题锻炼算法思维
`set`基于红黑树实现,支持有序存储、自动去重,增删查效率为O(logN)。通过仿函数可自定义排序规则,配合空间配置器灵活管理内存。不支持修改元素值,迭代器失效需注意。`multiset`允许重复元素。常用于去重、排序及查找场景。

热门文章

最新文章