Arrays.asList使用指南

在網(wǎng)上發(fā)現(xiàn)一篇講解 Arrays.asList 用法的好文章:Java Array to List Examples,我把文章要點(diǎn)整理如下,并加上一些個(gè)人見解,懇請(qǐng)各位看官斧正。

一、java.util.Arrays.asList() 的一般用法

??List 是一種很有用的數(shù)據(jù)結(jié)構(gòu),如果需要將一個(gè)數(shù)組轉(zhuǎn)換為 List 以便進(jìn)行更豐富的操作的話,可以這么實(shí)現(xiàn):

String[] myArray = { "Apple", "Banana", "Orange" }; 
List<String> myList = Arrays.asList(myArray);

??或者

List<String> myList = Arrays.asList("Apple", "Orange");

??上面這兩種形式都是十分常見的:將需要轉(zhuǎn)化的數(shù)組作為參數(shù),或者直接把數(shù)組元素作為參數(shù),都可以實(shí)現(xiàn)轉(zhuǎn)換。

二、極易出現(xiàn)的錯(cuò)誤及相應(yīng)的解決方案

錯(cuò)誤一: 將原生數(shù)據(jù)類型數(shù)據(jù)的數(shù)組作為參數(shù)

??前面說過,可以將需要轉(zhuǎn)換的數(shù)組作為 asList 方法的參數(shù)。假設(shè)現(xiàn)在需要轉(zhuǎn)換一個(gè)整型數(shù)組,那可能有人會(huì)想當(dāng)然地這么做:

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

??上面這段代碼的輸出結(jié)果是什么,會(huì)是3嗎?如果有人自然而然地寫出上面這段代碼的話,那么他也一定會(huì)以為 myList 的大小為3。很遺憾,這段代碼的輸出結(jié)果不是3,而是1。如果嘗試遍歷 myList ,你會(huì)發(fā)現(xiàn)得到的元素不是1、2、3中的任意一個(gè),而是一個(gè)帶有 hashCode 的對(duì)象。為什么會(huì)如此?
??來看一下asList 方法的簽名:

public static <T> List<T> asList(T... a)

??注意:參數(shù)類型是 T ,根據(jù)官方文檔的描述,T 是數(shù)組元素的 class
??如果你對(duì)反射技術(shù)比較了解的話,那么 class 的含義想必是不言自明。我們知道任何類型的對(duì)象都有一個(gè) class 屬性,這個(gè)屬性代表了這個(gè)類型本身。原生數(shù)據(jù)類型,比如 int,short,long等,是沒有這個(gè)屬性的,具有 class 屬性的是它們所對(duì)應(yīng)的包裝類 Integer,Short,Long。
??因此,這個(gè)錯(cuò)誤產(chǎn)生的原因可解釋為:asList 方法的參數(shù)必須是對(duì)象或者對(duì)象數(shù)組,而原生數(shù)據(jù)類型不是對(duì)象——這也正是包裝類出現(xiàn)的一個(gè)主要原因。當(dāng)傳入一個(gè)原生數(shù)據(jù)類型數(shù)組時(shí),asList 的真正得到的參數(shù)就不是數(shù)組中的元素,而是數(shù)組對(duì)象本身!此時(shí)List 的唯一元素就是這個(gè)數(shù)組。

解決方案:使用包裝類數(shù)組

??如果需要將一個(gè)整型數(shù)組轉(zhuǎn)換為 List,那么就將數(shù)組的類型聲明為 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());
   }
}

??這時(shí) myList 的大小就是3了,遍歷的話就得到1、2、3。這種方案是比較簡(jiǎn)潔明了的。
??其實(shí)在文章中,作者使用了另一種解決方案——他使用了 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());
   }
}

??因?yàn)槲覍?duì) Java 8 的這一新特性了解得不多,所以在此就不展開闡述,有興趣的朋友可以自行查閱相關(guān)資料。


錯(cuò)誤二:試圖修改 List 的大小

??我們知道 List 是可以動(dòng)態(tài)擴(kuò)容的,因此在創(chuàng)建一個(gè) List 之后最常見的操作就是向其中添加新的元素或是從里面刪除已有元素:

public class Test {
   public static void main(String[] args) {
      String[] myArray = { "Apple", "Banana", "Orange" };
      List<String> myList = Arrays.asList(myArray);
      myList.add("Guava");
   }
}

??嘗試運(yùn)行這段代碼,結(jié)果拋出了一個(gè) java.lang.UnsupportedOperationException 異常!這一異常意味著,向 myList 添加新元素是不被允許的;如果試圖從 myList 中刪除元素,也會(huì)拋出相同的異常。為什么會(huì)如此?
??仔細(xì)閱讀官方文檔,你會(huì)發(fā)現(xiàn)對(duì) asList 方法的描述中有這樣一句話:

返回一個(gè)由指定數(shù)組生成的固定大小的 List。

??謎底揭曉,用 asList 方法產(chǎn)生的 List 是固定大小的,這也就意味著任何改變其大小的操作都是不允許的。
??那么新的問題來了:按道理 List 本就支持動(dòng)態(tài)擴(kuò)容,那為什么偏偏 asList 方法產(chǎn)生的 List 就是固定大小的呢?如果要回答這一問題,就需要查看相關(guān)的源碼。Java 8 中 asList 方法的源碼如下:

@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

??方法中的的確確生成了一個(gè) ArrayList ,這不應(yīng)該是支持動(dòng)態(tài)擴(kuò)容的嗎?別著急,接著往下看。緊跟在 asList 方法后面,有這樣一個(gè)內(nèi)部類:

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

    //...
}

??這個(gè)內(nèi)部類也叫 ArrayList ,更重要的是在這個(gè)內(nèi)部類中有一個(gè)被聲明為 final 的數(shù)組 a ,所有傳入的元素都會(huì)被保存在這個(gè)數(shù)組 a 中。到此,謎底又揭曉了: asList 方法返回的確實(shí)是一個(gè) ArrayList ,但這個(gè) ArrayList 并不是 java.util.ArrayList ,而是 java.util.Arrays 的一個(gè)內(nèi)部類。這個(gè)內(nèi)部類用一個(gè) final 數(shù)組來保存元素,因此用 asList 方法產(chǎn)生的 ArrayList 是不可修改大小的。

解決方案:創(chuàng)建一個(gè)真正的 ArrayList

??既然我們已經(jīng)知道之所以asList 方法產(chǎn)生的 ArrayList 不能修改大小,是因?yàn)檫@個(gè) ArrayList 并不是“貨真價(jià)實(shí)”的 ArrayList ,那我們就自行創(chuàng)建一個(gè)真正的 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 了一個(gè) java.util.ArrayList ,然后再把 asList 方法的返回值作為構(gòu)造器的參數(shù)傳入,最后得到的 myList 自然就是可以動(dòng)態(tài)擴(kuò)容的了。

三、用自己的方法實(shí)現(xiàn)數(shù)組到 List 的轉(zhuǎn)換

??有時(shí),自己實(shí)現(xiàn)一個(gè)方法要比使用庫(kù)中的方法好。鑒于 asList 方法有一些限制,那么我們可以用自己的方法來實(shí)現(xiàn)數(shù)組到 List 的轉(zhuǎn)換:

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

??這么做自然也是可以達(dá)到目的的,但顯然有一個(gè)缺點(diǎn):代碼相對(duì)冗長(zhǎng),而且這么做其實(shí)無異于自己造輪子(reinventing the wheel)。當(dāng)然了,自己實(shí)現(xiàn)方法的好處也是顯而易見的,不管有什么需求,自己來滿足就好了,畢竟自己動(dòng)手豐衣足食嘛。比如說需要把數(shù)組的每個(gè)元素向 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);
         myList.add(str);
      }
      System.out.println(myList.size());
   }
}

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容