答覆: Java得到泛型類型

轉自Java得到泛型類型的回覆,內容有細微調整。 

Java泛型有這麼一種規律: 
位於聲明一側的,源碼裏寫了什麼到運行時就能看到什麼; 
位於使用一側的,源碼裏寫什麼到運行時都沒了。 

什麼意思呢?「聲明一側」包括泛型類型(泛型類與泛型接口)聲明、帶有泛型參數的方法和域的聲明。注意局部變量的聲明不算在內,那個屬於「使用」一側。 java

  1. import java.util.List;  
  2. import java.util.Map;  
  3.   
  4. public class GenericClass<T> {                // 1  
  5.     private List<T> list;                     // 2  
  6.     private Map<String, T> map;               // 3  
  7.       
  8.     public <U> U genericMethod(Map<T, U> m) { // 4  
  9.         return null;  
  10.     }  
  11. }  

上面代碼裏,帶有註釋的行裏的泛型信息在運行時都還能獲取到,原則是源碼裏寫了什麼運行時就能獲得什麼。針對1的GenericClass<T>,運行時經過Class.getTypeParameters()方法獲得的數組能夠獲取那個「T」;同理,2的T、3的java.lang.String與T、4的T與U均可以得到。源碼文本里寫的是什麼運行時就能獲得什麼;像是T、U等在運行時的實際類型是獲取不到的。 

這是由於從Java 5開始class文件的格式有了調整,規定這些泛型信息要寫到class文件中。以上面的map爲例,經過javap來看它的元數據能夠看到記錄了這樣的信息: 數組

private java.util.Map map;
  Signature: Ljava/util/Map;
  Signature: length = 0x2
   00 0A


乍一看,private java.util.Map map;不正好顯示了它的泛型類型被擦除了麼? 
但仔細看會發現有兩個Signature,下面的一個有兩字節的數據,0x0A。到常量池找到0x0A對應的項,是: 數據結構

const #10 = Asciz       Ljava/util/Map<Ljava/lang/String;TT;>;;


也就是內容爲「Ljava/util/Map<Ljava/lang/String;TT;>;」的一個字符串。 
根據Java 5開始的新class文件格式規範,方法與域的描述符增添了對泛型信息的記錄,用一對尖括號包圍泛型參數,其中普通的引用類型用「La/b/c/D;」的格式記錄,未綁定值的泛型變量用「Txxx;」的格式記錄,其中xxx就是源碼中聲明的泛型變量名。類型聲明的泛型信息也以相似下面的方式記了下來: app

public class GenericClass extends java.lang.Object
  Signature: length = 0x2
   00 12
// ...
const #18 = Asciz       <T:Ljava/lang/Object;>Ljava/lang/Object;;


詳細信息請參考官方文檔:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf 
該文檔也將會被整合到JVM規範第三版中。惋惜第三版如今只有草案,最終版本何時發佈還遙遙無期。 


相比之下,「使用一側」的泛型信息則徹底沒有被保留下來,在Java源碼編譯到class文件後就確實丟失了。也就是說,在方法體內的泛型局部變量、泛型方法調用之類的泛型信息編譯後都消失了。 jvm

import java.util.ArrayList;
import java.util.List;

public class TestClass {
    public static void main(String[] args) {
        List<String> list = null;       // 1
        list = new ArrayList<String>(); // 2
        for (int i = 0; i < 10; i++) ;
    }
}


上面代碼中,1留下的痕跡是:main()方法的StackMapTable屬性裏能夠看到: orm

StackMapTable: number_of_entries = 2
 frame_type = 253 /* append */
   offset_delta = 12
   locals = [ class java/util/List, int ]
 frame_type = 250 /* chop */
   offset_delta = 11


但這裏是沒有留下泛型信息的。這段代碼只因此寫了個空的for循環就是爲了迫使javac生成那個StackMapTable,讓1多留個影。當整個方法只有一個基本塊的時候javac就不會生成StackMapTable屬性,就看不到這可愛的數據結構了。 
若是main()裏用到了list的方法,那麼那些方法調用點上也會留下1的痕跡,例如若是調用list.add("");,則會留下「java/util/List.add:(Ljava/lang/Object;)Z」這種記錄。 
2留下的是「java/util/ArrayList."<init>":()V」,一樣也丟失了泛型信息。 

由上述討論可知,想對帶有未綁定的泛型變量的泛型類型獲取其實際類型是不現實的,由於class文件里根本沒記錄實際類型的信息。以爲這句話太拗口的話用例子來理解:要想對java.util.List<E>獲取E的實際類型是不現實的,由於List.class文件裏只記錄了E,卻沒記錄使用List<E>時E的實際類型。 
想對局部變量等「使用一側」的已綁定的泛型類型獲取其實際類型也不現實,一樣是由於class文件中根本沒記錄這個信息。例子直接看上面講「使用一側」的就能夠了。 

知道了什麼信息有記錄,什麼信息沒有記錄以後,也就能夠省點力氣不去糾結「拿不到T的實際類型」、「建不出T類型的數組」、「不能對T類型作instanceof」之類的問題了orz接口

相關文章
相關標籤/搜索