一、開門見山
兩種可以直接把要添加元素寫在參數列表裏的方法:
第一種: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 方式,本地方法拷貝會快得多,儘管拷貝兩次。