讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議100:數組的真實類型必須是泛型類型的子類型 >

建議100:數組的真實類型必須是泛型類型的子類型

List接口的toArray方法可以把一個集合轉化為數組,但是使用不方便,toArray()方法返回的是一個Object數組,所以需要自行轉變;toArray(Ta)雖然返回的是T類型的數組,但是還需要傳入一個T類型的數組,這也挺麻煩的,我們期望輸入的是一個泛型化的List,這樣就能轉化為泛型數組了,來看看能不能實現,代碼如下:


public static<T>TtoArray(List<T>list){

Tt=(T)new Object[list.size()];

for(int i=0,n=list.size();i<n;i++){

t[i]=list.get(i);

}

return t;

}


上面把要輸出的參數類型定義為Object數組,然後轉型為T類型數組,之後遍歷List賦值給數組的每個元素,這與ArrayList的toArray方法很類似(注意只是類似),客戶端的調用如下:


public static void main(Stringargs){

List<String>list=Arrays.asList("A","B");

for(String str:toArray(list)){

System.out.println(str);

}

}


編譯沒有任何問題,運行後出現如下異常:


Exception in thread"main"java.lang.ClassCastException:[Ljava.lang.Object;

cannot be cast to[Ljava.lang.String;at Client.main(Client.java:20)


類型轉換異常,也就是說不能把一個Object數組轉換為String數組,這段異常包含了兩個問題:

為什麼Object數組不能向下轉型為String數組?

數組是一個容器,只有確保容器內的所有元素類型與期望的類型有父子關係時才能轉換,Object數組只能保證數組內的元素是Object類型,卻不能確保它們都是String的父類型或子類,所以類型轉換失敗。

為什麼是main方法拋出異常,而不是toArray方法?

其實,是在toArray方法中進行的類型向下轉換,而不是main方法中。那為什麼異常會在main方法中拋出,應該在toArray方法的"Tt=(T)new Object[list.size()]"這段代碼才對呀?那是因為泛型是類型擦除的,toArray方法經過編譯後與如下代碼相同:


public static ObjecttoArray(List list){

//此處的強制類型沒必要存在,只是為了與源代碼對比

Objectt=(Object)new Object[list.size()];

for(int i=0,n=list.size();i<n;i++){

t[i]=list.get(i);

}

return t;

}

public static void main(Stringargs){

List<String>list=Arrays.asList("A","B");

for(String str:(String)toArray(list)){

System.out.println(str);

}

}


閱讀完此段代碼就很清楚了:toArray方法返回後會進行一次類型轉換,Object數組轉換成了String數組,於是就報ClassCastException異常了。

Object數組不能轉為String數組,T類型又無法在運行期獲得,那該如何解決這個問題呢?其實,要想把一個Obejct數組轉換為String數組,只要Object數組的實際類型(Actual Type)也是String就可以了,例如:


//objArray的實際類型和表面類型都是String數組

ObjectobjArray={"A","B"};

//拋出ClassCastException

StringstrArray=(String)objArray;

Stringss={"A","B"};

//objs的真實類型是String數組,顯示類型為Object數組

Objectobjs=ss;

//順利轉換為String數組

Stringstrs=(String)objs;


明白了這個問題,我們就把泛型數組聲明為泛型類的子類型吧!代碼如下:


public static<T>TtoArray(List<T>list, Class<T>tClass){

//聲明並初始化一個T類型的數組

Tt=(T)Array.newInstance(tClass, list.size());

for(int i=0,n=list.size();i<n;i++){

t[i]=list.get(i);

}

return t;

}


通過反射類Array聲明了一個T類型的數組,由於我們無法在運行期獲得泛型類型的參數,因此就需要調用者主動傳入T參數類型。此時,客戶端再調用就不會出現任何異常了。

在這裡我們看到,當一個泛型類(特別是泛型集合)轉變為泛型數組時,泛型數組的真實類型不能是泛型類型的父類型(比如頂層類Object),只能是泛型類型的子類型(當然包括自身類型),否則就會出現類型轉換異常。