java 泛型之我見 深入研究


Java的泛型在代碼中引用時,形式上很類似於普通的類,可以用來聲明對象、方法的參數與返回值類型,甚至還可以作強制類型轉換。因此,容易誤認為泛型是一個“類名的變量”,當泛型類被參數化引用的時候,這個“變量”就被“賦值”為實際類。事實上這種認識是一個誤區。

 

理論上說,Java中的類是由對應的.class文件的字節碼來定義的,類在其編譯時就會產生.class文件,與其如何被引用沒有任何關系,當然更不可能與引用時的參數相關。編寫泛型類時,也無從獲取實際引用時的參數類,如Class clz=T.class這樣的語句是無法通過編譯的(考慮到引用時參數類可以是通配符,這點也就不奇怪了)。

 

既然泛型不是類,也不能獲取引用時的參數類,那么以泛型聲明的對象,是以什么類型編譯的呢?答案是聲明泛型時的上界類。為了證實這一點,來看一個最簡單的自定義泛型類,只包含一個泛型聲明的屬性t及其get和set方法:

 

用jdk提供的javap工具可以查看.class文件的匯編代碼,結果如下:

 

可以看到,類體中用到的泛型T,編譯后都替換成了其上界類Number,而泛型特征在編譯后被完全抹去了。如果反編譯GenericClassTest.class文件,可以看的更清楚:

 

 

鑒於這種特性,在編寫泛型類代碼的時候要尤其謹慎使用泛型進行強制類型轉換,如T t=(T)(對象);這樣的語句在編譯時會被認作是強制類型轉換到泛型T的上界。由於編譯器無從得知T的准確類型,這種轉換無法用instanceof關鍵字預先進行匹配性的判斷(instanceof T無法通過編譯)。因此,如果使用不當,就會帶來強制類型轉換異常的隱患,尤其是轉換后的結果被引用的時候。如下是一個刻意造成這種錯誤的例子:

 

以上的泛型類在get方法中給泛型屬性t強制賦值一個字符串。由於T的編譯上界類是Object,這個語句可以通過編譯(但是會觸發“未經檢驗的類型轉換”警告)。引用時,聲明了一個以Integer為參數的對象gct,在gct的作用域(main函數)中,其屬性t被編譯器認為是Integer類型,因此將其賦值給整數對象i,編譯也不會有問題。但是運行時就會出現類型轉換異常

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

    at test.GenericClassTest.main(GenericClassTest.java:13)

 

既然泛型類編譯后只與泛型上界有關,為何在使用泛型類的對象時,又表現出強類型的特征呢(例如無法向一個ArrayList<String>聲明的對象中add一個整數)?事實上這是Java編譯器在引用泛型類的時候做了一次轉義。為此寫一個測試方法:

 

方法中聲明了一個泛型GenericClassTest的對象,引用參數為Integer,並且執行了一次set和get方法。反編譯.class文件,相應方法代碼如下:

 

可以看到,代碼中同樣沒有泛型特征,說明編譯器在正式編譯前,做了一步“預處理”,將涉及泛型引用的對象強制轉換為泛型參數所指定的類型,也就是Integer類(注:如果聲明泛型對象是參數帶有通配符,則原先的get和set方法只有一個可以被引用:指定上界時只能使用get方法,指定下界時只能使用set方法,否則編譯不通過。原因涉及通配符捕獲概念,參見http://www.ibm.com/developerworks/cn/java/j-jtp04298.html

 

接下編譯器才進行“真正的編譯”,生成.class文件。

 

這種預處理有什么用呢?好處就是編譯器寫好了一套規范來實現強制類型轉換,使得從使用外層看帶參的泛型可以當作強類型來用。如果有不正確的類型操作,在編譯階段就會及時報錯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM