java原始碼分析 Arrays.asList()與Collections.unmodifiableList()

anlian523發表於2020-02-12

舉個栗子

本章示例程式碼來自java程式設計思想——17.4.1未獲支援的操作——Unsupported類。

import java.util.*;

public class Unsupported {
    static void test(String msg, List<String> list) {
        System.out.println("--- " + msg + " ---");
        Collection<String> c = list;
        Collection<String> subList = list.subList(1,8);
        // Copy of the sublist:
        Collection<String> c2 = new ArrayList<String>(subList);
        try { c.retainAll(c2); } catch(Exception e) {
            System.out.println("retainAll(): " + e);
        }
        try { c.removeAll(c2); } catch(Exception e) {
            System.out.println("removeAll(): " + e);
        }
        try { c.clear(); } catch(Exception e) {
            System.out.println("clear(): " + e);
        }
        try { c.add("X"); } catch(Exception e) {
            System.out.println("add(): " + e);
        }
        try { c.addAll(c2); } catch(Exception e) {
            System.out.println("addAll(): " + e);
        }
        try { c.remove("C"); } catch(Exception e) {
            System.out.println("remove(): " + e);
        }
        // The List.set() method modifies the value but
        // doesn't change the size of the data structure:
        try {
            list.set(0, "X");
        } catch(Exception e) {
            System.out.println("List.set(): " + e);
        }
    }
    public static void main(String[] args) {
        List<String> list =
                Arrays.asList("A B C D E F G H I J K L".split(" "));
        test("Modifiable Copy", new ArrayList<String>(list));
        test("Arrays.asList()", list);
        test("unmodifiableList()",
                Collections.unmodifiableList(
                        new ArrayList<String>(list)));
    }
} /* Output:
--- Modifiable Copy ---
--- Arrays.asList() ---
retainAll(): java.lang.UnsupportedOperationException
removeAll(): java.lang.UnsupportedOperationException
clear(): java.lang.UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll(): java.lang.UnsupportedOperationException
remove(): java.lang.UnsupportedOperationException
--- unmodifiableList() ---
retainAll(): java.lang.UnsupportedOperationException
removeAll(): java.lang.UnsupportedOperationException
clear(): java.lang.UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll(): java.lang.UnsupportedOperationException
remove(): java.lang.UnsupportedOperationException
List.set(): java.lang.UnsupportedOperationException
*///:~

在主函式內先用Arrays這個工具類的asList方法生成了一個List。在呼叫靜態函式test方法時,先後測試了3種List:

  1. ArrayList的構造器,構造器簽名是:public ArrayList(Collection<? extends E> c)
  2. Arrays工具類的靜態方法asList方法,其方法簽名是public static <T> List<T> asList(T... a)
  3. Collections工具類的靜態方法unmodifiableList方法,其方法簽名是public static <T> List<T> unmodifiableList(List<? extends T> list)

這3種List的定義分別在:

  1. ArrayList
  2. Arrays$ArrayList(注意是Arrays的靜態內部類)
  3. Collections$UnmodifiableRandomAccessList(注意是Collections的靜態內部類)

在靜態方法test中,有一句Collection<String> subList = list.subList(1,8),這個list是傳入的形參,其型別是List<String>。而List<E>介面中的subList方法是沒有預設實現的,這意味著上面測試的3種List都是把subList方法進行了各自的實現了的。實現的位置分別在:

  1. ArrayList裡。
  2. 本應該在Arrays$ArrayList裡,但由於沒有override,所以實現在Arrays$ArrayList的父類AbstractList裡。
  3. Collections$UnmodifiableList裡或者Collections$UnmodifiableRandomAccessList裡(都是Collections的靜態內部類),由於主函式傳參是new ArrayList<String>(list))(ArrayList繼承了標記介面RandomAccess),所以靜態方法unmodifiableList方法返回的是Collections$UnmodifiableRandomAccessList的例項。

關於subList的各自實現本文不會深究,讀者只需要知道List<E>介面中的subList方法簽名是:List<E> subList(int fromIndex, int toIndex)即可。

靜態方法test裡,就是去測試型別為List<String>的傳入形參list,看它是否對於List<E>介面裡的各個可選操作是否都進行了實現。當List<E>的實現類不支援這些可選操作時,其實現會是直接丟擲UnsupportedOperationException異常。

從列印結果來看:

  1. ArrayList支援所有的可選操作(因為一個UnsupportedOperationException異常都沒有捕獲到),所以要想擁有完備的功能,還是得使用ArrayList啊。
  2. Arrays$ArrayList除了set()操作外,都不支援。
  3. Collections$UnmodifiableRandomAccessList都不支援,名副其實的Unmodifiable不可修改。

Arrays.asList()原始碼

public class Arrays {
    /*省略*/
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);//呼叫靜態內部類的構造器
    }

    /**
     * @serial include
     */
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable//看起來和真正的ArrayList好像差不多
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;//內部實現只是個陣列

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return a.clone();
        }

        @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;
        }

        @Override
        public E get(int index) {
            return a[index];//依靠陣列的取下標操作
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }

        @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;
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }

        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }

        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }

        @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]);
            }
        }

        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }
    }
    /*省略*/
}

Arrays.asList返回了一個fixed-size固定大小的List,它繼承AbstractList時除了實現了必要的get() & size()外,還實現了set()方法,畢竟set()方法不會違背fixed-size這個概念。

但是這個靜態內部類沒有去overrideadd() & remove(),這意味著它將使用繼承於AbstractList的實現——直接丟擲UnsupportedOperationException異常。所以在使用Arrays.asList返回的List時一定要注意到,add() & remove()方法我們是不能使用的。

在toArray方法裡面,可能會呼叫到Arrays.copyOf()方法。

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

三目表示式裡,先判斷(Object)newType == (Object)Object[].class)的返回值,這裡是想判斷一下傳入的Class物件的型別標籤是否為Object[],如果是那就好辦了,直接建立Object的陣列——new Object[newLength]。但其型別標籤如果不是Object[],又想建立新陣列,那就必須用到Array.newInstance了(注意是反射包裡的java.lang.reflect.Array)。

之所以一定要用三目表示式而不直接使用Array.newInstance,我個人猜想是:使用反射來建立陣列必定有著效率的問題,而如果型別標籤就是Object[],那麼就沒有必要使用反射了,直接建立Object陣列就可以更快執行。

注意在給Arrays.asList傳參時,不能將基本型別陣列作為引數。

import java.util.Arrays;
import java.util.List;

public class test1 {
    public static void main(String[] args) {
        int[] myArray = { 1, 2, 3 };
        List myList = Arrays.asList(myArray);
        System.out.println(myList.size());
    }
}

在這裡插入圖片描述
程式碼列印結果居然是1,而debug也看出來,myList的元素型別居然是int[3]。之所以列印結果不是3、且myList的元素型別不是int,是因為asList 方法簽名:

public static <T> List<T> asList(T... a)

可見其形參型別是T的可變引數,雖然java的自動拆裝箱配合泛型方法的型別推斷,能在單個基本型別時起到作用,但是遇到了基本型別的陣列時,自動拆裝箱就會失效了,所以List myList = Arrays.asList(myArray)這裡,程式認為你只傳入了一個引數,且這個引數的型別是int[]

相對的,在單個基本型別時,java的自動拆裝箱就可以配合泛型方法的型別推斷一起搞事情了,比如List myList = Arrays.asList(4)這句,debug效果如下:
在這裡插入圖片描述

Collections.unmodifiableList()原始碼

    public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
    }

unmodifiableList方法先判斷形參list是否屬於RandomAccess的,RandomAccess是一個標記介面,代表可以隨機存取,比如一般List就可以隨機訪問(其實就是其內部實現是陣列,陣列取下標操作當然就是隨機訪問了),而Set就不可以隨機訪問。根據是否屬於RandomAccess,返回相應的例項。

    static class UnmodifiableList<E> extends UnmodifiableCollection<E>
                                  implements List<E> {
        private static final long serialVersionUID = -283967356065247728L;

        final List<? extends E> list;

        UnmodifiableList(List<? extends E> list) {
            super(list);
            this.list = list;
        }

        public boolean equals(Object o) {return o == this || list.equals(o);}
        public int hashCode()           {return list.hashCode();}

        public E get(int index) {return list.get(index);}//非可選操作裡,就實現了get
        public E set(int index, E element) {//其他的非可選操作,都是拋異常
            throw new UnsupportedOperationException();
        }
        public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public E remove(int index) {
            throw new UnsupportedOperationException();
        }
        public int indexOf(Object o)            {return list.indexOf(o);}
        public int lastIndexOf(Object o)        {return list.lastIndexOf(o);}
        public boolean addAll(int index, Collection<? extends E> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            throw new UnsupportedOperationException();
        }
        @Override
        public void sort(Comparator<? super E> c) {
            throw new UnsupportedOperationException();
        }

        public ListIterator<E> listIterator()   {return listIterator(0);}

        public ListIterator<E> listIterator(final int index) {
            return new ListIterator<E>() {
                private final ListIterator<? extends E> i
                    = list.listIterator(index);

                public boolean hasNext()     {return i.hasNext();}
                public E next()              {return i.next();}
                public boolean hasPrevious() {return i.hasPrevious();}
                public E previous()          {return i.previous();}
                public int nextIndex()       {return i.nextIndex();}
                public int previousIndex()   {return i.previousIndex();}

                public void remove() {//當然,ListIterator也直接拋異常,如果改成呼叫i成員的remove,可能不會拋異常的
                    throw new UnsupportedOperationException();
                }
                public void set(E e) {
                    throw new UnsupportedOperationException();
                }
                public void add(E e) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public void forEachRemaining(Consumer<? super E> action) {
                    i.forEachRemaining(action);
                }
            };
        }

        public List<E> subList(int fromIndex, int toIndex) {
            return new UnmodifiableList<>(list.subList(fromIndex, toIndex));
        }

        /**
         * UnmodifiableRandomAccessList instances are serialized as
         * UnmodifiableList instances to allow them to be deserialized
         * in pre-1.4 JREs (which do not have UnmodifiableRandomAccessList).
         * This method inverts the transformation.  As a beneficial
         * side-effect, it also grafts the RandomAccess marker onto
         * UnmodifiableList instances that were serialized in pre-1.4 JREs.
         *
         * Note: Unfortunately, UnmodifiableRandomAccessList instances
         * serialized in 1.4.1 and deserialized in 1.4 will become
         * UnmodifiableList instances, as this method was missing in 1.4.
         */
        private Object readResolve() {
            return (list instanceof RandomAccess
                    ? new UnmodifiableRandomAccessList<>(list)
                    : this);
        }
    }

    /**
     * @serial include
     */
    static class UnmodifiableRandomAccessList<E> extends UnmodifiableList<E>
                                              implements RandomAccess
    {
        UnmodifiableRandomAccessList(List<? extends E> list) {
            super(list);
        }

        public List<E> subList(int fromIndex, int toIndex) {
            return new UnmodifiableRandomAccessList<>(
                list.subList(fromIndex, toIndex));
        }

        private static final long serialVersionUID = -2542308836966382001L;

        /**
         * Allows instances to be deserialized in pre-1.4 JREs (which do
         * not have UnmodifiableRandomAccessList).  UnmodifiableList has
         * a readResolve method that inverts this transformation upon
         * deserialization.
         */
        private Object writeReplace() {
            return new UnmodifiableList<>(list);
        }
    }

UnmodifiableList的實現其實就是套一個殼子而已,所以操作都依賴於構造器裡傳入的那個List。

UnmodifiableList是名副其實的不可修改,那些常用操作裡面,它就只實現了get()方法,連set()都不讓用。注意Collections$unmodifiableList還繼承了Collections$UnmodifiableCollection,後者已經幫忙實現了size() isEmpty() contains() iterator()等方法,當然它的實現風格也是讓很多方法拋UnsupportedOperationException異常。

UnmodifiableRandomAccessList的實現就簡單,直接繼承UnmodifiableList就好了。注意這二者的區別就是:UnmodifiableRandomAccessList代表了可隨機訪問,UnmodifiableList代表了不可以隨機訪問。

注意到,UnmodifiableRandomAccessList有一個writeReplace方法必然返回一個非隨機訪問的不可修改列表;而UnmodifiableList有一個readResolve方法可能返回一個隨機訪問的不可修改列表。從這兩個方法的邏輯可以理解出:隨機訪問的列表可以直接轉換為非隨機訪問的列表(writeReplace方法),但非隨機訪問的列表要想轉換為隨機訪問的列表的前提是,此列表原本就是可隨機訪問的(readResolve方法)。這種關係就類似於父類物件和子類物件之間的互相轉換。

相關文章