Array 和 List 都是我們在開發過程中常見的資料結構。我們都知道 Array 是定長的,List 是可變長。而且,List 的實現類 ArrayList 也是根據 Array 去實現的。
以下 Array 指代陣列,List 指代陣列列表。
Array 轉 List
當然最原始的方法就是使用遍歷的方式,將 Array 中的元素都新增到 List 中。這種實現方式這裡不作贅述。
Java1.2 之後,Jdk 語言提供 Arrays 這個工具類。大大簡化了我們常見的 Array 操作,但是也有不少需要注意的問題。
如下:
Integer[] a = { 1, 2 };
List<Integer> l = Arrays.asList(a);
這是我們常見的 Array 轉換 List 的方式,但是這個使用上有一個問題。當對 List 物件 l 進行列表插入操作時:
l.add(3);
程式就會丟擲異常 java.lang.UnsupportedOperationException
。這是為什麼呢?
檢視 Arrays.asList
原始碼發現,
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
這裡返回的 ArrayList 並不是 java.util.ArrayList
而是 java.util.Arrays.ArrayList
。Arrays 又新建了一個 ArrayList 內部類,實現了一些基本 get set 方法。
回頭檢視 java.util.Arrays.ArrayList.ArrayList(E[] array)
建構函式,
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
不難發現,java.util.Arrays.ArrayList
雖然打著 List 的旗號,繼承了 AbstractList
。但是其只是在 Array 的基礎上進行了簡單的封轉,AbstractList
中則是直接重寫了 add 方法,表示這個方法是不允許操作。
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
明白了這個錯誤產生的原因,回頭想一下 Java 的這些開發者們為什麼這樣設計。
ArrayList 中如果要新增一個元素,則需要先對其內部的 Array 進行擴容,然後將 Old Array 複製到擴容後的 New Array 中。如果 Array 轉 List 僅僅是讀取操作,或是在 Array 的 Size 範圍之內進行替換操作,再將 Array 複製一遍,不免會對記憶體進行浪費,倒不如直接將原始的 Array 直接拿來維護更為直接和高效(正如java.util.Arrays.ArrayList
的實現方式)。
明白這個緣由之後,如果要在 Array 轉 List 之後,不只有隻讀操作,那麼則需要下面的實現,
List<Integer> l = new ArrayList<>(Arrays.asList(a));
雖然在我們日常的開發過程中,已經習慣了使用 ArrayList 去代替 Array,但是瞭解此處 Java 的轉換過程還是能夠讓我們少踩坑。
List 轉 Array
因為 Array 的長度不可變,所以這個轉換過程中,會有長度不匹配的情況。
常見的轉換方法是 ArrayList.toArray()
或 ArrayList.toArray(T[])
。
這兩個實現的共同點是都是對 ArrayList 中的 Array 進行 Copy 操作,生成一個新的陣列返回。不同點是前者返回值是 Object[]
,後者是 T[]
。
在 ArrayList.toArray(T[])
的使用過程中需要注意,當要轉換的 Array 長度小於 ArrayList 的 size 時,不要試圖通過傳入形參的方式進行轉換,雖然這在 Array 的長度大於 List 時不會出現問題。
如下程式碼:
// l [1, 2, 3]
Integer[] a = new Integer[2];
l.toArray(a); // error 正確寫法:a = l.toArray(a);
Stream.of(a).forEach(System.out::println);
輸出結果是:null null
。
檢視原始碼實現:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a`s runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
// 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;
}
可見,當 a.length < size
成立,入參 a
並沒有被使用,所以 a
依然是 new Integer[2]
。
所以,極度建議在轉換之前初始化 Array 的長度為 ArrayList 的 size,並且使用返回值重新給 Array 賦值。
// l [1, 2, 3]
Integer[] b = new Integer[l.size()];
l.toArray(b);
Stream.of(b).forEach(System.out::println);
原文地址:https://xdbin.com/blog/8a9eec5167da2d4501681e8c3f180001