一、java.util.Arrays.asList() 的一般用法
List 是一種很有用的數據結構,如果需要將一個數組轉換為 List 以便進行更豐富的操作的話,可以這么實現:
String[] myArray = { "Apple", "Banana", "Orange" }; List<String> myList = Arrays.asList(myArray);
或者
List<String> myList = Arrays.asList("Apple", "Orange");
上面這兩種形式都是十分常見的:將需要轉化的數組作為參數,或者直接把數組元素作為參數,都可以實現轉換。
二、極易出現的錯誤及相應的解決方案
錯誤一: 將原生數據類型數據的數組作為參數
前面說過,可以將需要轉換的數組作為 asList 方法的參數。假設現在需要轉換一個整型數組,那可能有人會想當然地這么做:
public class Test { public static void main(String[] args) { int[] myArray = { 1, 2, 3 }; List myList = Arrays.asList(myArray); System.out.println(myList.size()); } }
上面這段代碼的輸出結果是什么,會是3嗎?如果有人自然而然地寫出上面這段代碼的話,那么他也一定會以為 myList 的大小為3。很遺憾,這段代碼的輸出結果不是3,而是1。如果嘗試遍歷 myList ,你會發現得到的元素不是1、2、3中的任意一個,而是一個帶有 hashCode 的對象。為什么會如此?
來看一下asList 方法的簽名:
public static <T> List<T> asList(T... a)
注意:參數類型是 T ,根據官方文檔的描述,T 是數組元素的 class。
如果你對反射技術比較了解的話,那么 class 的含義想必是不言自明。我們知道任何類型的對象都有一個 class 屬性,這個屬性代表了這個類型本身。原生數據類型,比如 int,short,long等,是沒有這個屬性的,具有 class 屬性的是它們所對應的包裝類 Integer,Short,Long。
因此,這個錯誤產生的原因可解釋為:asList 方法的參數必須是對象或者對象數組,而原生數據類型不是對象——這也正是包裝類出現的一個主要原因。當傳入一個原生數據類型數組時,asList 的真正得到的參數就不是數組中的元素,而是數組對象本身!此時List 的唯一元素就是這個數組。
解決方案:使用包裝類數組
如果需要將一個整型數組轉換為 List,那么就將數組的類型聲明為 Integer 而不是 int。
public class Test { public static void main(String[] args) { Integer[] myArray = { 1, 2, 3 }; List myList = Arrays.asList(myArray); System.out.println(myList.size()); } }
這時 myList 的大小就是3了,遍歷的話就得到1、2、3。這種方案是比較簡潔明了的。
其實在文章中,作者使用了另一種解決方案——他使用了 Java 8 新引入的 API:
public class Test { public static void main(String[] args) { int[] intArray = { 5, 10, 21 }; //Java 8 新引入的 Stream 操作 List myList = Arrays.stream(intArray).boxed().collect(Collectors.toList()); } }
因為我對 Java 8 的這一新特性了解得不多,所以在此就不展開闡述,有興趣的朋友可以自行查閱相關資料。
錯誤二:試圖修改 List 的大小
我們知道 List 是可以動態擴容的,因此在創建一個 List 之后最常見的操作就是向其中添加新的元素或是從里面刪除已有元素:
public class Test { public static void main(String[] args) { String[] myArray = { "Apple", "Banana", "Orange" }; List<String> myList = Arrays.asList(myArray); myList.add("Guava"); } }
嘗試運行這段代碼,結果拋出了一個 java.lang.UnsupportedOperationException
異常!這一異常意味着,向 myList 添加新元素是不被允許的;如果試圖從 myList 中刪除元素,也會拋出相同的異常。為什么會如此?
仔細閱讀官方文檔,你會發現對 asList 方法的描述中有這樣一句話:
返回一個由指定數組生成的固定大小的 List。
謎底揭曉,用 asList 方法產生的 List 是固定大小的,這也就意味着任何改變其大小的操作都是不允許的。
那么新的問題來了:按道理 List 本就支持動態擴容,那為什么偏偏 asList 方法產生的 List 就是固定大小的呢?如果要回答這一問題,就需要查看相關的源碼。Java 8 中 asList 方法的源碼如下:
@SafeVarargs @SuppressWarnings("varargs") public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
方法中的的確確生成了一個 ArrayList ,這不應該是支持動態擴容的嗎?別着急,接着往下看。緊跟在 asList 方法后面,有這樣一個內部類:
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; } //... }
這個內部類也叫 ArrayList ,更重要的是在這個內部類中有一個被聲明為 final 的數組 a ,所有傳入的元素都會被保存在這個數組 a 中。到此,謎底又揭曉了: asList 方法返回的確實是一個 ArrayList ,但這個 ArrayList 並不是 java.util.ArrayList ,而是 java.util.Arrays 的一個內部類。這個內部類用一個 final 數組來保存元素,因此用 asList 方法產生的 ArrayList 是不可修改大小的。
解決方案:創建一個真正的 ArrayList
既然我們已經知道之所以asList 方法產生的 ArrayList 不能修改大小,是因為這個 ArrayList 並不是“貨真價實”的 ArrayList ,那我們就自行創建一個真正的 ArrayList :
public class Test { public static void main(String[] args) { String[] myArray = { "Apple", "Banana", "Orange" }; List<String> myList = new ArrayList<String>(Arrays.asList(myArray)); myList.add("Guava"); } }
在上面這段代碼中,我們 new 了一個 java.util.ArrayList ,然后再把 asList 方法的返回值作為構造器的參數傳入,最后得到的 myList 自然就是可以動態擴容的了。
三、用自己的方法實現數組到 List 的轉換
有時,自己實現一個方法要比使用庫中的方法好。鑒於 asList 方法有一些限制,那么我們可以用自己的方法來實現數組到 List 的轉換:
public class Test { public static void main(String[] args) { String[] myArray = { "Apple", "Banana", "Orange" }; List<String> myList = new ArrayList<String>(); for (String str : myArray) { myList.add(str); } System.out.println(myList.size()); } }
這么做自然也是可以達到目的的,但顯然有一個缺點:代碼相對冗長,而且這么做其實無異於自己造輪子(reinventing the wheel)。當然了,自己實現方法的好處也是顯而易見的,不管有什么需求,自己來滿足就好了,畢竟自己動手豐衣足食嘛。