美文网首页java学习之路
JavaGuide知识点整理——集合使用注意事项总结

JavaGuide知识点整理——集合使用注意事项总结

作者: 唯有努力不欺人丶 | 来源:发表于2022-07-11 21:52 被阅读0次

    这篇文章是根据阿里巴巴java开发手册总结了关于集合使用常见的逐一实现以及原理。

    集合判空

    判断所有集合内部的元素是否为空使用isEmpty()方法,而不是size()==0的方式。

    这是因为isEmpty() 方法的可读性更好,并且时间复杂度为O(1).不过也有很多复杂度不是O(1)的,比如JUC包下的某些集合ConcurrentLinkedQueue 、ConcurrentHashMap...
    下面是ConcurrentHashMap的size()方法和isEmpty()方法的源码:

    public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }
    final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }
    public boolean isEmpty() {
        return sumCount() <= 0L; // ignore transient negative values
    }
    

    集合转Map

    在使用java.util.stream.Collectors类的toMap()方法转Map集合时,一定要注意value为null时会抛出空指针异常。

    下面我们来解释一下原因:
    首先,java.util.stream.Collectors类的toMap()方法可以看到其内部调用了Map接口的merge()方法。


    Collectors源码 merge方法

    而merge方法源码如下:


    merge方法中判空

    所以由上可以看出如果value值为空会报空指针。

    集合遍历

    不要再foreach循环里进行元素的remove/add操作。remove元素请使用Irerator方式,如果并发操作,需要对Iterator对象加锁。

    通过反编译你会发现foreach语法糖底层其实还是以来Iterator,不过remove/add操作直接调用的是集合自己的方法,而不会Iterator的remove/add方法。

    这就导致Iterator莫名其妙的发现自己的元素被remove/add,然后就会抛出一个并发修改的异常(ConcurrentModificationException).这就是单线程状态线上线下产生的fail-fast机制。

    fail-fast机制:多个线程对fail-fast集合进行修改的时候,可能抛出ConcurrentModificationException。即使是单线程的情况下也有可能会出现这种情况。
    除了上面介绍的直接使用Iterator进行遍历操作以外,还可以:

    • 使用普通的for循环
    • 使用fail-safe的集合类。java.util包下的所有的集合类都是fail-fast的,而java.util.concurrent包下的所有类都是fail-safe的。

    集合去重

    可以利用Set元素唯一的特性,快速对一个集合进行去重操作,避免使用List的contains()进行遍历去重或者判断包含操作。
    这里用HashSet和ArrayList为例说明。

    // Set 去重代码示例
    public static <T> Set<T> removeDuplicateBySet(List<T> data) {
    
        if (CollectionUtils.isEmpty(data)) {
            return new HashSet<>();
        }
        return new HashSet<>(data);
    }
    
    // List 去重代码示例
    public static <T> List<T> removeDuplicateByList(List<T> data) {
    
        if (CollectionUtils.isEmpty(data)) {
            return new ArrayList<>();
    
        }
        List<T> result = new ArrayList<>(data.size());
        for (T current : data) {
            if (!result.contains(current)) {
                result.add(current);
            }
        }
        return result;
    }
    

    两者的核心在于contains()方法的实现。
    HashSet的contains()方法底部以来HashMap的containsKey()方法。时间复杂度接近于O(1).
    我们有n个元素插入Set,时间复杂度就是O(n).
    ArrayList的contains()方法是通过遍历所有的元素来实现的。时间复杂度接近O(n).所以List有n个元素,时间复杂度是O(n^2)

    集合转数组

    使用集合转数组的方法,必须使用集合的toArray(),传入的是类型完全一致,长度为0的空数组。

    String [] s= new String[]{
        "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
    };
    List<String> list = Arrays.asList(s);
    Collections.reverse(list);
    //没有指定类型的话会报错
    s=list.toArray(new String[0]);
    

    由于JVM优化,new String[0]作为Collection.toArray()方法的参数现在使用更好。new String[0]就是起了一个模板的作用,指定返回数组的类型,0是为了节省空间。

    数组转集合

    使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法。它的 add/remove/clear方法会抛出UnsupportedOperationException异常。

    1. Arrays.asList()是泛型方法,传递的数组必须是对象数组,不能是基本类型的。
      当传入一个原生数据类型的时候,Arrays.asList()的真正得到的参数就不是数组中的元素,而是数组对象本身。此时List的唯一元素就是这个数组。所以有如下代码:
    int[] myArray = {1, 2, 3};
    List myList = Arrays.asList(myArray);
    System.out.println(myList.size());//1
    System.out.println(myList.get(0));//数组地址值
    System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
    int[] array = (int[]) myList.get(0);
    System.out.println(array[0]);//1
    

    我们可以用包装类解决这个问题。

    1. 使用集合的修改方法add,remove,clear会抛出异常
      因为Arrays.asList() 方法返回的并不是java.util.ArrayList,而是java.util.Arrays的一个内部类,这个内部类并没有实现(重写)集合的修改方法。

    我们如何正确的将数组转化为ArrayList?

    1. 手动实现工具类
    static <T> List<T> arrayToList(final T[] array) {
      final List<T> l = new ArrayList<T>(array.length);
    
      for (final T s : array) {
        l.add(s);
      }
      return l;
    }
    Integer [] myArray = { 1, 2, 3 };
    System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
    
    1. 最简单的方法
    List list = new ArrayList<>(Arrays.asList("a", "b", "c"));
    
    1. 使用java8的stream
    Integer [] myArray = { 1, 2, 3 };
    List myList = Arrays.stream(myArray).collect(Collectors.toList());
    //基本类型也可以实现转换(依赖boxed的装箱操作)
    int [] myArray2 = { 1, 2, 3 };
    List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
    
    1. 使用 工具类
    List<String> list = new ArrayList<String>();
    CollectionUtils.addAll(list, str);
    
    1. Java9的 List.of()方法
    Integer[] array = {1, 2, 3};
    List<Integer> list = List.of(array);
    

    本篇笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!

    相关文章

      网友评论

        本文标题:JavaGuide知识点整理——集合使用注意事项总结

        本文链接:https://www.haomeiwen.com/subject/goygbrtx.html