轉自Java得到泛型類型的回覆,內容有細微調整。
Java泛型有這麼一種規律:
位於聲明一側的,源碼裏寫了什麼到運行時就能看到什麼;
位於使用一側的,源碼裏寫什麼到運行時都沒了。
什麼意思呢?「聲明一側」包括泛型類型(泛型類與泛型接口)聲明、帶有泛型參數的方法和域的聲明。注意局部變量的聲明不算在內,那個屬於「使用」一側。 java
上面代碼裏,帶有註釋的行裏的泛型信息在運行時都還能獲取到,原則是源碼裏寫了什麼運行時就能獲得什麼。針對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接口