Java list安全删除元素详解

背景

前一段时间被问到了关于 List 集合的安全删除元素问题。一时间没反应过来这问题问的是什么,安全体现在什么地方,线程安全?线程安全可以保证元素粒度的数据唯一吗?删除是指什么,list.remove()?
带着这些疑问,重温了一下Java的集合知识。

问题分析

List为什么需要安全移除?

我不理解什么是安全删除,我开发的业务中也很少说需要用到remove的,我只记得一般用的话,都是remove(index)这样。写个测试代码看看

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

for (int i = 0; i < list.size(); i++) {
    if ("B".equals(list.get(i))) {
        list.remove(i);
        continue;
    }
    System.out.println(list.get(i));
}

这段代码的目的就是想把B移除,最后期望的输出只有AC两个字母,看一下运行结果:
在这里插入图片描述
目的达到了,那这个删除不就是安全删除吗?怎么才算安全删除?

于是我又加了一堆 A B C D E F G,还是删除B,最后打印的数组也是正确的。

直到我突发奇想,多加了一个连续重复的字母B,问题出现了

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("B");
list.add("C");
list.add("D");

for (int i = 0; i < list.size(); i++) {
    if ("B".equals(list.get(i))) {
        list.remove(i);
        continue;
    }
//            System.out.println(list.get(i));
}
System.out.println(list);

我打印的结果是

[A, B, C, D]

为什么会这样呢?其实原理很简单,就是因为List.remove删除元素后,数组的整体下标会往前移动,原本的位置被遍历过了,就会被跳过。

ABBCD遍历index
012340遍历元素A,不作任何操作
012341遍历第一个B,移除B
01232遍历C,已经跳过第二个B
01233遍历D,后面没有元素了,结束

那简单啊,我记得list移除操作可以remove(object),试一下

for (String element : list) {
    if (element.equals("B")) {
        // 在for循环中直接使用list.remove()方法删除元素
        list.remove(element);
    }
}
System.out.println(list);

直接给我报错了
在这里插入图片描述
分析了一下ArrayList的源码,原来增强for循环的实现原理是使用了Iterator迭代器,而ArrayList重写了迭代器的next方法,每次迭代时会检查是否做了新增或者删除操作(modCount++),而这些操作都会导致期待值与实际值不对等,从而抛出异常。
说简单点就是和两个B字母无关,是你使用了增强for循环就不可以在遍历的时候add和remove。

和上面相同的代码原理是这样的,使用迭代器遍历list,随后用ArrayList的remove

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            if (element.equals("B")) {
                list.remove(element); // 直接使用list.remove()方法删除元素
            }
        }

试了一下,也是报错,一模一样的问题。

到现在为止,我们理解了怎样场景下移除元素是不安全的。不安全包括:

  1. 下标上移导致的检查丢失
  2. ConcurrentModificationException的发生

问题解决

方案一:

查阅了Java的API文档之后,上面提到,使用Iterator自己的remove方法可以安全地移除元素。

        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("B");
        list.add("C");
        list.add("D");
        
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            if (element.equals("B")) {
                iterator.remove(); // 安全移除元素
            }
        }

        System.out.println(list);
        

输出的结果为

[A, C, D]

方案二

Java8之后list新增了一个api removeIf,这个也可以做安全删除

list.removeIf(s -> s.equals("B"));

输出的结果为

[A, C, D]

方案三

使用removeAll方法

        List<String> elementsToRemove = new ArrayList<>();
        for (String element : list) {
            if (element.equals("B")) {
                elementsToRemove.add(element);
            }
        }

        list.removeAll(elementsToRemove);

这样执行的结果也是正确的

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的List是一个有序的集合,可以存储重复元素。List接口继承自Collection接口,提供了一些常用的方法,例如添加、删除、查询等。List接口有多个实现类,例如ArrayList、LinkedList、Vector等等,每个实现类都有不同的特点和适用场景。以下是一些常用的List方法: 1. add(E e):在List的末尾添加元素e。 2. add(int index, E e):在List的指定位置index添加元素e。 3. remove(Object o):从List中删除指定元素o。 4. remove(int index):从List中删除指定位置index的元素。 5. set(int index, E e):将List中指定位置index的元素替换为e。 6. get(int index):获取List中指定位置index的元素。 7. size():获取List中元素的个数。 8. indexOf(Object o):返回List中指定元素o的索引,如果不存在则返回-1。 9. subList(int fromIndex, int toIndex):返回List中从fromIndex到toIndex(不包括toIndex)的子列表。 以下是一些常用的List实现类及其特点: 1. ArrayList:基于数组实现,支持快速随机访问元素,但在插入和删除元素时性能较差,适用于读取和遍历操作较多的场景。 2. LinkedList:基于双向链表实现,支持快速插入和删除元素,但在随机访问元素时性能较差,适用于插入和删除操作较多的场景。 3. Vector:与ArrayList类似,但是线程安全,适用于多线程环境。 总之,ListJava中常用的集合类型之一,提供了丰富的API,可以方便地进行元素的添加、删除、查询等操作。在选择List实现类时,需要根据具体的应用场景选择合适的实现类,以获得更好的性能和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值