• Java Collection 移除元素的几种方式


    1. 前言

    操作集合是一个 Java 编程人员几乎每天都在重复的事情。今天我们来研究一下从 Java Collection 中删除元素的方法。我构建了一个简单的集合,我们以此为例子来展开探索。

      List<String> servers = new ArrayList<>();
            servers.add("Felordcn");
            servers.add("Tomcat");
            servers.add("Jetty");
            servers.add("Undertow");
            servers.add("Resin");
    

    2. for 循环并不一定能从集合中移除元素

    让我们使用传统的 foreach 循环移除 F 开头的假服务器,但是你会发现这种操作引发了 ConcurrentModificationException 异常。

     // 错误的示范 千万不要使用
      for (String server : servers) {
        if (server.startsWith("F")) {
            servers.remove(server);
        }
     }
    

    难道 for 循环就不能移除元素了吗?当然不是!我们如果能确定需要被移除的元素的索引还是可以的。

     // 这种方式是可行
     for (int i = 0; i < servers.size(); i  ) {
        if (servers.get(i).startsWith("F")) {
            servers.remove(i);
        }
    }
    

    但是这种方式我目前只演示了 ArrayList,其它的类型并没有严格测试,留给你自己探索。

    3. 迭代器 Iterator 可以删除集合中的元素

    在传统方式中我们使用 Iterator 是可以保证删除元素的:

        Iterator<String> iterator = servers.iterator();
    
            while (iterator.hasNext()) {
                String next = iterator.next();
                if (next.startsWith("F")) {
                    iterator.remove();
                }
            }
    

    4. 遍历删除元素的缺点

    • 我们需要遍历集合的每一个元素并对它们进行断言,哪怕你删除一个元素。
    • 尽管我们可以通过迭代的方式删除特定的元素,但是操作繁琐,根据集合类型的不同有潜在的 ConcurrentModificationException 异常。
    • 根据数据结构的不同,删除元素的时间复杂度也大大不同。比如数组结构的 ArrayList 在删除元素的速度上不如链表结构的 LinkedList

    5. 新的集合元素删除操作

    Java 8 提供了新的集合操作 APIStream 来帮助我们解决这个问题。我在以前的文章中已经介绍了 Java 8 Stream API,如果有兴趣可以去看看。

    5.1 Collection.removeIf()

    新的 Collection Api removeIf(Predicate<? super E> filter) 。该 Api 提供了一种更简洁的使用 Predicate (断言)删除元素的方法,于是我们可以更加简洁的实现开始的需求:

     servers.removeIf(s-> s.startsWith("F"));
    

    同时根据测试,ArrayListLinkedList 的性能接近。一般推荐使用这种方式进行操作。

    5.2 Stream 实现移除元素

    和上面所有移除操作不同的是,其实任何操作都不会改变 Stream 源,我们仅仅是使用 Stream Api 操作数据源的副本。遵循了 数据源 -> 中间操作 -> 归纳终止 的生命周期。我们来看看使用 Stream 如何实现我们的意图。

    5.2.1 通过 filter 断言实现

    我们可以使用 Streamfilter 断言。filter 断言会把符合断言的流元素汇集成一个新的流,然后归纳起来即可,于是我们可以这么写:

    // 跟以上不同的是 该方式中的断言是取反的操作。
    List<String> newServers = servers.stream().filter(s -> !s.startsWith("F")).collect(Collectors.toList());
    

    这个优点上面已经说了不会影响原始数据,生成的是一个副本。缺点就是可能会有内存占用问题。

    5.2.2 通过 Collectors.partitioningBy 归纳

    这种方法虽然可以满足需要但是我感觉有点投机取巧的成份。Collectors.partitioningBy() 方法本意是做二分类的。该方法会将流中符合断言的、不符合断言的元素分别归纳到两个 key 分别为 truefalseMap 中,我们可以归类得到符合和不符合的元素集。实现如下:

     Map<Boolean, List<String>> f = servers.stream().collect(Collectors.partitioningBy(s -> !s.startsWith("F")));
            
      List<String> trues = f.get(Boolean.TRUE);
      System.out.println("不以 F 开头的:  "   trues);
    
      List<String> falses = f.get(Boolean.FALSE);
      System.out.println("以 F 开头的:  "   falses);
    

    一般该方式不推荐在此场景使用,并不符合该 Api 的设计意图。

    6. 总结

    今天我们研究了一些从 Collections 中删除元素的方法 及其注意事项。不知道你有没有其它的实现方式,不妨通过公众号:Felordcn 告诉我。

    关注公众号:Felordcn获取更多资讯

    个人博客:https://felord.cn

  • 相关阅读:
    react中React.createRef()的使用
    react中this指向问题
    react中对props进行限制
    react中this问题
    react 中this问题
    类中方法的this
    类中方法的this
    react中render函数里面的this指向?
    Android一对一直播系统源码开发,仿朋友圈发布动态的实现
    Android游戏陪玩源码开发中,阴影效果的实现
  • 原文地址:https://www.cnblogs.com/felordcn/p/12142470.html
Copyright © 2020-2023  润新知