1. 程式人生 > 其它 >ArrayList有參構造方法 中 elementData.getClass() != Object[].class 的探究

ArrayList有參構造方法 中 elementData.getClass() != Object[].class 的探究

在月讀ArrayList原始碼的時候,在ArrayList的構造方法ArrayList(Collection<? extends E> c) 中看到了一行程式碼,感覺有些困惑,elementData.getClass() != Object[].class 為啥會不等呢?

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class
) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }  

於是就輸出相關程式碼來進行測試驗證:

public static void main(String[] args) {
        Object[] oo = {};
        List<String> list = new ArrayList<>();
        list.add("aa");
        System.out.println(list.getClass());
        System.out.println(list.toArray().getClass());
        System.out.println(list.toArray().getClass()== oo.getClass());
        System.out.println();

        list = Arrays.asList("abc");
        System.out.println(list.getClass());
        System.out.println(list.toArray().getClass());
        System.out.println(list.toArray().getClass()== oo.getClass());
        System.out.println();

        System.out.println(oo.getClass());
    }

執行結果:

可以看到兩次輸出list的型別都不一樣,一個是java.util.ArrayList,一個是java.util.Arrays$ArrayList;

list呼叫toArray()方法後得到的Class型別也不一樣,一個是java.lang.Object,一個是java.lang.String,

所以用他們和Object[]的型別進行比較的時候,也不一樣,一個true,一個false。

解釋:為什麼兩次list不一樣?

第一個是直接使用new ArrayList()例項化的物件,所以他的型別是java.uitl.ArrayList。第二個是使用的是Arrays.asList()方法初始化的物件,看方法asList()的原始碼,如下所示:

/**
     * Returns a fixed-size list backed by the specified array.  (Changes to
     * the returned list "write through" to the array.)  This method acts
     * as bridge between array-based and collection-based APIs, in
     * combination with {@link Collection#toArray}.  The returned list is
     * serializable and implements {@link RandomAccess}.
     *
     * <p>This method also provides a convenient way to create a fixed-size
     * list initialized to contain several elements:
     * <pre>
     *     List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
     * </pre>
     *
     * @param <T> the class of the objects in the array
     * @param a the array by which the list will be backed
     * @return a list view of the specified array
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }  

該方法返回的是一個ArrayList,但是此ArrayList非彼ArrayList,這個返回的ArrayList是Arrays自己的一個靜態內部類,如下所示:

/**
     * @serial include
     */
    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

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

       //···
    }   

可以看到這個靜態內部類實現了RandomAccess、Serializable介面,即支援快速隨機訪問,支援序列化,這就支援了,向java.util.ArrayList的轉化。所以asList(T... a)方法的註釋上,有一句很妙的解釋:

This method acts as bridge between array-based and collection-based APIs, in combination with {@link Collection#toArray}.

此方法與 {@link Collection#toArray} 相結合,充當基於陣列和基於集合的 API 之間的橋樑

所以第二個list輸出型別是java.util.Arrays$ArrayList 也就是Arrays自己的ArrayList,並且這個list底層也是陣列。

解釋:為什麼list呼叫toArray()方法後得到的Class型別也不一樣,一個是java.lang.Object,一個是java.lang.String ??

先看一個例子:

public static void main(String[] args) {
        String[] s = {"a"};
        System.out.println(s.getClass());
        System.out.println();

        String[][] ss = {{"a"}};
        System.out.println(ss.getClass());
        System.out.println();

        String[] a = {"a"};
        Integer[] b = {1};
        Object[] c = {new Object()};
        System.out.println(a.getClass());
        System.out.println(b.getClass());
        System.out.println(c.getClass());
    }  

輸出如圖:

紅方框中表示的是陣列的維度,s是一維陣列, ss是二維陣列,最後看後三行,發現a、b、c輸出的是String、Integer、Object,這說明了一個問題,對陣列進行getClass()方法呼叫,得到的是陣列用來存放元素的型別

回到問題,為什麼list呼叫toArray()方法後得到的Class型別也不一樣,一個是java.lang.Object,一個是java.lang.String ??

第一個list,也就是直接用ArrayList例項化的list,他的toArray()方法的原始碼,如下:

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    } 

返回的是一個明確的Object型別的陣列物件,因此呼叫第一個list的toArray()後在呼叫getClass()方法 輸出的就是java.lang.Object

第二個list,被Arrays.asList()例項化後的list,他的toArray()方法原始碼,如下:(注意這裡應該是Arrays自己的ArrayList中的toList方法)

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        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();
        }
        //...
}  

可以看到Arrays的ArrayList的toArray()方法返回的是 a.clone(),而a的宣告型別為 E [ ],也就是泛型化的,即ArrayList被例項化為什麼型別的,a.clone()就是什麼型別的。

程式碼中Arrays.asList("abc"),是String[ ]型別,所以這裡呼叫toArray()後,返回a.clone()方法得到的是一個String [ ], 對String[ ]在呼叫getClass()方法,得到的就是java.lang.String

所以在java.util.ArrayList的有參構造方法中,會有一行型別判斷

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }  

所以這裡要加上特殊情況,如果陣列緩衝區elementData的元素個數大於0,並且型別不是Object[ ]的,就要通過Arrays.copyOf()方法,來轉化為Object[ ]