藉助可變參數一次創建包含多個元素的 ArrayList

一、開門見山

兩種可以直接把要添加元素寫在參數列表裏的方法:
第一種:Arrays.asList 方式

List<Integer> l1 = new ArrayList<>(Arrays.asList(1, 2, 3));

第二種:Collections.addAll 方式

List<Integer> l2 = new ArrayList<>();
Collections.addAll(l2, 1, 2, 3);

二、抽絲剝繭

ArrayList 自身的構造函數和兩個 addAll 方法都不支持可變參數,
public boolean addAll(Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c)

2.1 Arrays.asList 方式

Arrays.asList 實現:

	// asList 方法
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
	
	// Arrays 內部靜態類 ArrayList,和 java.util 包下的 ArrayList 同名,兩者沒有關係
    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() { // toArray 方法藉助 Object 的 clone 方法實現
            return a.clone();
        }
        
        // ...
    }

這裏的 ArrayList 是 Arrays 的靜態內部類,最大的特點是不能增刪元素。
以 asList 可變參數數組爲底層存儲結構。
其中 toArray 方法通過 Object 的 clone 方法實現:

protected native Object clone() throws CloneNotSupportedException;

ArrayList 參數爲 Collection 的構造函數:

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

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

Arrays.copyOf()方法詳解-jdk1.8
大致要點:
newType 的類型是 Class<? extends T[]>,T 是一個泛型,如果與 Object 做比較的話,父子關係肯定是 Object >= T 的,由於 == 只能在同類型中才能做比較,所以需要強制轉換。
class 對象是單例模式,所以可以用 == 比較。
方法目的:數組向上轉型並拷貝


總結前面的代碼,待添加元素經過兩次拷貝後,添加成功。

  • 第一次拷貝發生在,ArrayList 構造函數的第一句 elementData = c.toArray();,通過 Object 的 clone 方法進行了一次拷貝。
  • 第二次拷貝還是發生在 ArrayList 構造函數,elementData = Arrays.copyOf(elementData, size, Object[].class);

解釋一下爲什麼 elementData.getClass() != Object[].class 爲 true:
因爲執行完 Arrays.asList(1, 2, 3) 後,底層的數組類型爲 Integer[]。
所以下面這段代碼的執行結果爲 true,true。

import java.util.Arrays;

public class Test {
	public static void main(String[] args){
		System.out.println(Arrays.asList(1, 2, 3).toArray().getClass() == Integer[].class);
		// 顯式類型聲明
		System.out.println(Arrays.<Object>asList(1, 2, 3).toArray().getClass() == Object[].class);
	}
}

2.2 Collections.addAll 方式

    public static <T> boolean addAll(Collection<? super T> c, T... elements) {
        boolean result = false;
        for (T element : elements)
            result |= c.add(element);
        return result;
    }
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

三、結論

初始元素少的話,直接寫在可變參數裏,喜歡用哪種就用哪種,幾個元素的添加不會有什麼效率問題。
初始元素多的話,比如添加一個大數組,推薦使用 Arrays.asList 方式,本地方法拷貝會快得多,儘管拷貝兩次。

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