讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 第7章 泛型和反射 >

第7章 泛型和反射

Don\'t let complexity stop you.Be activists.Take on the big inequities.It will be one of the great experiences of your lives.

不要讓這個世界的複雜性阻礙你的前進。要成為一個行動主義者,將解決人類的不平等視為己任。它將成為你生命中最重要的經歷之一。

——比爾·蓋茨在哈佛大學的演講

泛型可以減少強制類型的轉換,可以規範集合的元素類型,還可以提高代碼的安全性和可讀性,正是因為有這些優點,自從Java引入泛型後,項目的編碼規則上便多了一條:優先使用泛型。

反射可以「看透」程序的運行情況,可以讓我們在運行期知曉一個類或實例的運行狀況,可以動態地加載和調用,雖然有一定的性能憂患,但它帶給我們的便利遠遠大於其性能缺陷。

建議93:Java的泛型是類型擦除的

Java泛型(Generic)的引入加強了參數類型的安全性,減少了類型的轉換,它與C++中的模板(Templates)比較類似,但是有一點不同的是:Java的泛型在編譯期有效,在運行期被刪除,也就是說所有的泛型參數類型在編譯後都會被清除掉,我們來看一個例子,代碼如下:


public class Foo{

//arrayMethod接收數組參數,並進行重載

public void arrayMethod(StringstrArray){

}

public void arrayMethod(IntegerintArray){

}

//listMethod接收泛型List參數,並進行重載

public void listMethod(List<String>stringList){

}

public void listMethod(List<Integer>intList){

}

}


程序很簡單,編寫了4個方法,arrayMethod方法接收String數組和Integer數組,這是一個典型的重載,listMethod接收元素類型為String和Integer的List變量。現在的問題是,這段程序是否能編譯?如果不能,問題出在什麼地方?

事實上,這段程序是無法編譯的,編譯時報錯信息如下:


Method listMethod(List<Integer>)has the same erasure listMethod(List<E>)as

another method in type Foo


此錯誤的意思是說listMethod(List<Integer>)方法在編譯時擦除類型後的方法是listMethod(List<E>),它與另外一個方法重複,通俗地說就是方法簽名重複。這就是Java泛型擦除引起的問題:在編譯後所有的泛型類型都會做相應的轉化。轉換規則如下:

List<String>、List<Integer>、List<T>擦除後的類型為List。

List<String>擦除後的類型為List。

List<?extends E>、List<?super E>擦除後的類型為List<E>。

List<T extends Serializable&Cloneable>擦除後為List<Serializable>。

明白了這些擦除規則,再看如下代碼:


public static void main(Stringargs){

List<String>list=new ArrayList<String>();

list.add(\"abc\");

String str=list.get(0);

}


經過編譯器的擦除處理後,上面的代碼與下面的程序是一致的:


public static void main(Stringargs){

List list=new ArrayList();

list.add(\"abc\");

String str=(String)list.get(0);

}


Java編譯後的字節碼中已經沒有泛型的任何信息了,也就是說一個泛型類和一個普通類在經過編譯後都指向了同一字節碼,比如Foo<T>類,經過編譯後將只有一份Foo.class類,不管是Foo<String>還是Foo<Integer>引用的都是同一字節碼。Java之所以如此處理,有兩個原因:

避免JVM的大換血。C++的泛型生命期延續到了運行期,而Java是在編譯器擦除掉的,我們想想,如果JVM也把泛型類型延續到運行期,那麼JVM就需要進行大量的重構工作了。

版本兼容。在編譯期擦除可以更好地支持原生類型(Raw Type),在Java 1.5或1.6平台上,即使聲明一個List這樣的原生類型也是可以正常編譯通過的,只是會產生警告信息而已。

明白了Java的泛型是類型擦除的,我們就可以解釋類似如下的問題了:

(1)泛型的class對象是相同的

每個類都有一個class屬性,泛型化不會改變class屬性的返回值,例如:


public static void main(Stringargs){

List<String>ls=new ArrayList<String>();

List<Integer>li=new ArrayList<Integer>();

System.out.println(li.getClass()==li.getClass());

}


以上代碼將返回為true,原因很簡單,List<String>和List<Integer>擦除後的類型都是List,沒有任何區別。

(2)泛型數組初始化時不能聲明泛型類型

如下代碼編譯時通不過:


List<String>listArray=new List<String>;


原因很簡單,可以聲明一個帶有泛型參數的數組,但是不能初始化該數組,因為執行了類型擦除操作,List<Object>與List<String>就是同一回事了,編譯器拒絕如此聲明。

(3)instanceof不允許存在泛型參數

以下代碼不能通過編譯,原因一樣,泛型類型被擦除了:


List<String>list=new ArrayList<String>();

System.out.println(list instanceof List<String>);