長度不可變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 啦,根據不同的實用場景,實用這些比較特殊的集合,可以很好的提高大家的程序的處理效率,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章