长度不可变List中Collections.singletonList()与Arrays.asList()的使用

前言

集合是开发中最常用到的一种数据存储结构,我们常用的长度可变的集合,因为比较方便,但是由于每次长度的变化都会涉及已有对象的全量拷贝,所以在该处是非常浪费时间的,所以我们再日常开发中如果是要放入集合中的数据是可以提前确定数据量的话,那就最好可以初始化一个超过该集合存储阈值的集合空间,避免集合中存储空间不够的情况下进行的自动的自己空间的扩容和对象的拷贝。还有一些是比较特殊的场景,那就是我们初始化集合的时候对象和对象的数量已经是确定的,并且整个过程中不会去插入新的对象,这个时候我们再用一个可变的集合来存储就有点大材小用,浪费空间啦,这个时候,我们下面的主角就要登场啦,他们分别是两个非常常用的不可变长度的集合:Collections.singletonListArrays.asList,接下来我们从不同的角度来全面的解读一下这两个集合,这样有助于我们在日常开发中写出更多高效、稳定的代码。在正式开始介绍前,我们先列一个简单的提纲,让大家可以有条理的去看后面的内容:


  • Collections.singletonList和Arrays.asList的简介
  • Collections.singletonList 源码介绍
  • Collections.singletonList 特点介绍
  • Arrays.asList 源码介绍
  • Arrays.asList 特点介绍
  • Collections.singletonList和Arrays.asList的区别

简介

了解一个知识的原因有很多种,而我了解这两种List的原因也是很独特的,是我在开发的过程中通过Arrays.asList的这种方式创建了一个只有一个对象的List,IDE突然提示我通过Collections.singletonList这种方式更好,这不由的勾起了我的好奇心,然后就深入的研究了一下这两种方式初始化的List的区别,以免在使用的过程中出现不可预知的问题,希望下面的梳理对你有用。首先这两种方式创建的List的最大相同点就是都继承了抽象类AbstractList,其它地方可以说它们有着天壤之别,下面我们就来详细的了解一下他们的各自特点吧。

  • 提示:下面的源码分析的JDK版本是JDK8,

Collections.singletonList : 长度为1

源码分析

public static <T> List<T> singletonList(T o) {
        return new SingletonList<>(o);
    }

    //私有的静态内部类SingletonList,继承了抽象类AbstractList,实现了RandomAccess和Serializable,实现RandomAccess接口是为了让该类支持快速随机访问,实现Serializable接口是为了实现序列化
    private static class SingletonList<E>
        extends AbstractList<E>
        implements RandomAccess, Serializable {

        private static final long serialVersionUID = 3093736618740652951L;
        //这是SingletonList最大的特点,保存的只是一个不可修改的元素,而不是一个元素数组
        private final E element;
        //SingletonList只有一个构造方法,该构造方法初始化了集合中唯一的一个元素
        SingletonList(E obj)                {element = obj;}
        //迭代器直接返回SingletonList中的唯一一个元素
        public Iterator<E> iterator() {
            return singletonIterator(element);
        }
        //SingletonList中有且只有一个元素,所以直接返回常量1
        public int size()                   {return 1;}
        
        //直接通过判断唯一的元素与传入对象是否相等来进行判断是否存在,return o1==null ? o2==null : o1.equals(o2);
        public boolean contains(Object obj) {return eq(obj, element);}
        
        //只有一个元素,只能get(0),否则返回 IndexOutOfBoundsException 异常
        public E get(int index) {
            if (index != 0)
              throw new IndexOutOfBoundsException("Index: "+index+", Size: 1");
            return element;
        }

        //重写了forEach方法,将唯一的一个元素传入循环中
        @Override
        public void forEach(Consumer<? super E> action) {
            action.accept(element);
        }
        
        //由于只有一个元素并且是final类型的,不支持removeIf方法
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            throw new UnsupportedOperationException();
        }
        //由于只有一个元素并且是final类型的,不支持replaceAll方法
        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            throw new UnsupportedOperationException();
        }
        
        //由于只有一个元素,排序也就没有任何意义啦,所以重写了排序的方法,然后没有做任何的处理
        @Override
        public void sort(Comparator<? super E> c) {
        }
        
        //对List中的元素进行拆分,由于只有一个元素,拆分后也还是这个元素
        @Override
        public Spliterator<E> spliterator() {
            return singletonSpliterator(element);
        }
    }

特点介绍

SingletonList这个静态内部类的源码其实很简单,大家只要细心一定就可以看明白:

  • 调用Collections.singletonList(T o)方法其实是创建了一个SingletonList对象,SingletonList继承了抽象类AbstractList,同时实现了RandomAccess和Serializable
  • SingletonList最大的特点是整个集合中只能有一个元素,所以重写了size方法,直接返回了1,也没有重写add方法,这样如果我们去调用add方法的话就会直接抛出AbstractList中对它的原始实现,直接抛出UnsupportedOperationException异常。
  • 同时SingletonList也重写了一些List中常用的一些方法,根据该List只有一个元素这个特点,做了一些最为优化的处理。
  • SingletonList中唯一的一个元素可以设置为null。
  • 由于SingletonList中唯一的一个元素是final类型的,所以一旦被初始化完成后就不可以进行修改,通过例如set这样的一些方法来进行修改的话会直接抛出UnsupportedOperationException异常,只能通过各种不同的方式来进行读取这个唯一的元素。

Arrays.asList:长度不可变

源码分析


public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    //私有的静态内部类ArrayList,继承了抽象类AbstractList,实现了RandomAccess和Serializable,实现RandomAccess接口是为了让该类支持快速随机访问,实现Serializable接口是为了实现序列化,该处有一个大坑需要大家注意一下,这里的ArrayList和我们在日常开发中常用的那个ArrayList并不是同一个类,只是他们都继承了AbstractList,但是这两个不同的类由于他们的作用不一样,所以他们对List接口和AbstractList抽象类中的一些常用方法的实现方式和丰富度有很大差别,大家在使用的时候一定要注意这里
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        
        //该处的ArrayList用一个用final关键字修饰的不可变数组来存储元素
        private final E[] a;
        
        //ArrayList唯一的一个构造方法,构造方法的传入参数必须是一个数组,并且不允许为null
        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }
        
        //重写了size方法,返回了该数组的长度,由于数组是final类型的,所以不需要考虑长度的变化
        @Override
        public int size() {
            return a.length;
        }
        
        //重写了toArray方法,将不可变的私有数组进行拷贝并返回
        @Override
        public Object[] toArray() {
            return a.clone();
        }
        
        //重写了toArray方法,将 ArrayList 中的元素拷贝到传入的数组中并返回
        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
        
        //重写了get方法,数组初始化完成后就不可变,直接返回数组对应下标的元素
        @Override
        public E get(int index) {
            return a[index];
        }
        
        //重写了set方法,初始化完成后数组的长度是不可变的,但是里面的元素是可以通过set方法来进行修改的
        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }
        
        //重写了indexOf方法,初始化完成后数组的长度是不可变的,整体出来比较简单,传入参数可以为null
        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
            if (o == null) {
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }
        
        //重写了contains方法,传入参数可以为null,直接通过for循环来判断数组中是否存在该元素
        @Override
        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }
        
        //重写了spliterator方法
        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }
        
        //重写了forEach方法,直接将 ArrayList 中的每一个元素放入循环中
        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }
        
        //重写了replaceAll方法,不允许传入参数为null,由于 ArrayList 的长度不可变,直接通过for循环来进行替换操作
        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }
        
        //重写了sort方法,对 ArrayList 中的元素进行排序
        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }
    }

特点介绍

ArrayList 这个静态内部类的源码也是比较简单,整体来说要比 SingletonList 稍微复杂一些 :

  • 调用 Arrays.asList(T… a)方法其实是创建了一个 ArrayList 对象,ArrayList 继承了抽象类 AbstractList,同时实现了RandomAccess和Serializable两个接口
  • ArrayList 最大的特点是整个集合允许有多个元素存入数组中,但是一旦初始化后,数组的长度就不可以再进行任何的更改,但是数组中的元素可以修改。
  • 同时 ArrayList 也重写了一些 AbstractList 中常用的一些方法,根据 ArrayList 初始化完成后长度不可变的这个特点,做了一些最为优化的处理。
  • ArrayList 中的传入的数组参数中允许存在null,但是不允许只有一个null。
  • 由于 ArrayList 中存储数组是final类型的,所以一旦被初始化完成后长度就不可以进行修改,所以我们可以去遍历 ArrayList 中的元素,可以去修改 ArrayList 中的元素,但是我们不能去增加或者删除ArrayList 中的元素。

Collections.singletonList和Arrays.asList的区别

从上面整体的去梳理两个不同的集合的源码,接下来我们就来总结一下这两个集合的区别和适合的使用场景

  • Collections.singletonList 和 Arrays.asList 都是长度不可变的集合,Collections.singletonList 长度为1,元素初始化完成就不可修改, Arrays.asList 长度不可变,元素初始化完成还可以进行修改,根据这个特点,我们在开发中如果是遇到只需要存储一个元素的集合,并且整个过程存储或者传递为主,不会进行修改或者调整的话,强烈推荐用Collections.singletonList ,因为这里面对所有的获取元素的方法都做了最为简单的处理,对整体的时间和资源的消耗都是最小的;如果开发中碰到长度完全可以确定的集合,并且在初始化前已经确定了存储元素的话,强烈推荐用 Arrays.asList ,该集合结合长度不可变的特点,对该集合中的常用方法都进行了非常好的优化,避免了我们在处理过程中去判断由于元素增加造成的长度变化和元素拷贝,效率也是非常高的。
  • Collections.singletonList 中的唯一一个元素可以是null,但是如果 Arrays.asList 只存入一个元素的话,那就一定不允许为null啦,否则的话会抛出 NullPointerException 异常,这个地方我们在使用的时候一定要注意
  • Arrays.asList 中创建的 ArrayList 和我们常用的java.util.ArrayList并不是同一个,所以不要以为 java.util.ArrayList 中可以使用的方法在 Arrays.asList 中也都可以使用,否则的话分分钟教你重新做人。
  • Collections.singletonList 中保存元素的是一个对象, Arrays.asList 中保存元素的是一个数组,在这一点上,它俩的差别还是很大的,当然,如果你喜欢,你也可以在 Collections.singletonList中保存一个数组对象。

总结

通过上面的整体的梳理,我们应该对 Collections.singletonList 和 Arrays.asList 有了一个比较深刻的理解,大家在接下来的开发中也可以根据实际的情况来进行更加多样化的选择,而不是不管三七二十一都去 new ArrayList 啦,根据不同的实用场景,实用这些比较特殊的集合,可以很好的提高大家的程序的处理效率,谢谢。

猜你喜欢

转载自blog.csdn.net/zhangzehai2234/article/details/106040080