讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議66:asList方法產生的List對像不可更改 >

建議66:asList方法產生的List對像不可更改

上一個建議指出了asList方法在轉換基本類型數組時存在的問題,接著我們看一下asList方法返回的列表有何特殊的地方,代碼如下所示:


enum Week{Sun, Mon, Tue, Wed, Thu, Fri, Sat}

public static void main(Stringargs){

//五天工作制

WeekworkDays={Week.Mon, Week.Tue, Week.Wed, Week.Thu, Week.Fri};

//轉換為列表

List<Week>list=Arrays.asList(workDays);

//增加週六也為工作日

list.add(Week.Sat);

/*工作日開始幹活了*/

}


很簡單的程序呀,默認聲明的工作日(workDays)是從週一到週五,偶爾週六也會算作工作日加入到工作日列表中。不過,這段程序執行時會不會有什麼問題呢?

編譯沒有任何問題,但是一運行,卻出現了如下結果:


Exception in thread"main"java.lang.UnsupportedOperationException

at java.util.AbstractList.add(AbstractList.java:131)

at java.util.AbstractList.add(AbstractList.java:91)


UnsupportedOperationException,不支持的操作?居然不支持List的add方法,這真是奇怪了!還是來追根尋源,看看asList方法的源代碼:


public static<T>List<T>asList(T……a){

return new ArrayList<T>(a);

}


直接new了一個ArrayList對像返回,難道ArrayList不支持add方法?不可能呀!可能,問題就出在這個ArrayList類上,此ArrayList非java.util.ArrayList,而是Arrays工具類的一個內置類,其構造函數如下所示:


//這是一個靜態私有內部類

private static class ArrayList<E>extends AbstractList<E>

implements RandomAccess, java.io.Serializable{

//存儲列表元素的數組

private final Ea;

//唯一的構造函數

ArrayList(Earray){

if(array==null)

throw new NullPointerException();

a=array;

}

/*其他方法省略*/

}


這裡的ArrayList是一個靜態私有內部類,除了Arrays能訪問外,其他類都不能訪問。仔細看這個類,它沒有提供add方法,那肯定是父類AbstractList提供了,來看代碼:


public boolean add(E e){

throw new UnsupportedOperationException();

}


父類確實提供了,但沒有提供具體的實現(源代碼上是通過add方法調用add(int, E)方法來實現的,為了便於講解,此處縮減了代碼),所以每個子類都需要自己覆寫add方法,而Arrays的內部類ArrayList沒有覆寫,因此add一個元素就會報錯了。

我們再深入地看看這個ArrayList靜態內部類,它僅僅實現了5個方法:

size:元素數量。

toArray:轉化為數組,實現了數組的淺拷貝。

get:獲得指定元素。

set:重置某一元素值。

contains:是否包含某元素。

對於我們經常使用的List.add和List.remove方法它都沒有實現,也就是說asList返回的是一個長度不可變的列表,數組是多長,轉換成的列表也就是多長,換句話說此處的列表只是數組的一個外殼,不再保持列表動態變長的特性,這才是我們要關注的重點(雖然此處JDK的設計有悖OO設計原則,但這不在我們討論的範圍內,而且我們也無力回天)。

有些開發者特別喜歡通過如下方式定義和初始化列表:


List<String>names=Arrays.asList("張三","李四","王五");


一句話完成了列表的定義和初始化,看似很便捷,卻深藏著重大隱患——列表長度無法修改。想想看,如果這樣一個List傳遞到一個允許add操作的方法中,那將會產生何種結果?如果讀者有這種習慣,請慎之戒之,除非非常自信該Lis只用於讀操作。