1. 程式人生 > >【轉載】[Java]Arrays.asList()使用指南

【轉載】[Java]Arrays.asList()使用指南

在網上發現一篇講解 Arrays.asList 用法的好文章:Java Array to List Examples,我把文章要點整理如下,並加上一些個人見解,懇請各位看官斧正。

一、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 方法的引數。假設現在需要轉換一個整型陣列,那可能有人會想當然地這麼做:

1 public class Test {
2    public static
void main(String[] args) { 3 int[] myArray = { 1, 2, 3 }; 4 List myList = Arrays.asList(myArray); 5 System.out.println(myList.size()); 6 } 7 }

 

  上面這段程式碼的輸出結果是什麼,會是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。

1 public class Test {
2    public static void main(String[] args) {
3       Integer[] myArray = { 1, 2, 3 };
4       List myList = Arrays.asList(myArray);
5       System.out.println(myList.size());
6    }
7 }

 

  這時 myList 的大小就是3了,遍歷的話就得到1、2、3。這種方案是比較簡潔明瞭的。
  其實在文章中,作者使用了另一種解決方案——他使用了 Java 8 新引入的 API:

1 public class Test {
2    public static void main(String[] args) {
3       int[] intArray = { 5, 10, 21 };
4       //Java 8 新引入的 Stream 操作
5       List myList = Arrays.stream(intArray).boxed().collect(Collectors.toList());
6    }
7 }

 

  因為我對 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 方法後面,有這樣一個內部類:

 1 private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable
 2 {
 3     private static final long serialVersionUID = -2764017481108945198L;
 4     private final E[] a;
 5 
 6     ArrayList(E[] array) {
 7         a = Objects.requireNonNull(array);
 8     }
 9 
10     @Override
11     public int size() {
12         return a.length;
13     }
14 
15     //...
16 }

 

  這個內部類也叫 ArrayList ,更重要的是在這個內部類中有一個被宣告為 final 的陣列 a ,所有傳入的元素都會被儲存在這個陣列 a 中。到此,謎底又揭曉了: asList 方法返回的確實是一個 ArrayList ,但這個 ArrayList 並不是 java.util.ArrayList ,而是 java.util.Arrays 的一個內部類。這個內部類用一個 final 陣列來儲存元素,因此用 asList 方法產生的 ArrayList 是不可修改大小的。

解決方案:建立一個真正的 ArrayList

  既然我們已經知道之所以asList 方法產生的 ArrayList 不能修改大小,是因為這個 ArrayList 並不是“貨真價實”的 ArrayList ,那我們就自行建立一個真正的 ArrayList :

1 public class Test {
2    public static void main(String[] args) {
3       String[] myArray = { "Apple", "Banana", "Orange" };
4       List<String> myList = new ArrayList<String>(Arrays.asList(myArray));
5       myList.add("Guava");
6    }
7 }

 

  在上面這段程式碼中,我們 new 了一個 java.util.ArrayList ,然後再把 asList 方法的返回值作為構造器的引數傳入,最後得到的 myList 自然就是可以動態擴容的了。

三、用自己的方法實現陣列到 List 的轉換

  有時,自己實現一個方法要比使用庫中的方法好。鑑於 asList 方法有一些限制,那麼我們可以用自己的方法來實現陣列到 List 的轉換:

 1 public class Test {
 2    public static void main(String[] args) {
 3       String[] myArray = { "Apple", "Banana", "Orange" };
 4       List<String> myList = new ArrayList<String>();
 5       for (String str : myArray) {
 6          myList.add(str);
 7       }
 8       System.out.println(myList.size());
 9    }
10 }

 

  這麼做自然也是可以達到目的的,但顯然有一個缺點:程式碼相對冗長,而且這麼做其實無異於自己造輪子(reinventing the wheel)。當然了,自己實現方法的好處也是顯而易見的,不管有什麼需求,自己來滿足就好了,畢竟自己動手豐衣足食嘛。比如說需要把陣列的每個元素向 List 中新增兩次:

 1 public class Test {
 2    public static void main(String[] args) {
 3       String[] myArray = { "Apple", "Banana", "Orange" };
 4       List<String> myList = new ArrayList<String>();
 5       for (String str : myArray) {
 6          myList.add(str);
 7          myList.add(str);
 8       }
 9       System.out.println(myList.size());
10    }
11 }

 

  總之,問題的解決方案往往不止一個,為了效率我們往往會選擇使用已有輪子來決解問題,但如果沒有任何限制的話不妨試試別的方案。



作者:臨江聽雨客
連結:https://www.jianshu.com/p/2b113f487e5e