程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 詳解Java泛型type體系整理

詳解Java泛型type體系整理

編輯:關於JAVA

一直對jdk的ref使用比較模糊,早上花了點時間簡單的整理了下,也幫助自己理解一下泛型的一些處理。

Java中class,method,fIEld的繼承體系

Java中所有對象的類型定義類Type

說明:

Type : Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, array types, type variables and primitive types.

使用

一般我們不直接操作Type類型,所以第一次使用會對這個比較陌生,相對內部的一些概念。

根據Type類型分類,整理了一個type -> class的轉換過程,同理也包括處理Generic Type。支持多級泛型處理。

Java代碼

  1. private static Class getClass(Type type, int i) {
  2. if (type instanceof ParameterizedType) { // 處理泛型類型
  3. return getGenericClass((ParameterizedType) type, i);
  4. } else if (type instanceof TypeVariable) {
  5. return (Class) getClass(((TypeVariable) type).getBounds()[0], 0); // 處理泛型擦拭對象
  6. } else {// class本身也是type,強制轉型
  7. return (Class) type;
  8. }
  9. }
  10. private static Class getGenericClass(ParameterizedType parameterizedType, int i) {
  11. Object genericClass = parameterizedType.getActualTypeArguments()[i];
  12. if (genericClass instanceof ParameterizedType) { // 處理多級泛型
  13. return (Class) ((ParameterizedType) genericClass).getRawType();
  14. } else if (genericClass instanceof GenericArrayType) { // 處理數組泛型
  15. return (Class) ((GenericArrayType) genericClass).getGenericComponentType();
  16. } else if (genericClass instanceof TypeVariable) { // 處理泛型擦拭對象
  17. return (Class) getClass(((TypeVariable) genericClass).getBounds()[0], 0);
  18. } else {
  19. return (Class) genericClass;
  20. }
  21. }

測試代碼:

Java代碼

  1. interface GeneircInteface {
  2. T method1(T obj);
  3. }
  4. interface CommonInteface {
  5. Integer method2(Integer obj);
  6. }
  7. class BaseGeneircInteface implements GeneircInteface {
  8. protected R result;
  9. @Override
  10. public R method1(R obj) {
  11. return obj;
  12. }
  13. }
  14. class GenericClass extends BaseGeneircInteface> implements GeneircInteface>, CommonInteface {
  15. @Override
  16. public List method1(List obj) {
  17. result = obj;
  18. return result;
  19. }
  20. public Integer method2(Integer obj) {
  21. return obj;
  22. }
  23. public extends Throwable> T method3(T obj) throws E {
  24. return obj;
  25. }
  26. }

針對class的泛型接口使用:

Java代碼

  1. private static void classGeneric() {
  2. System.out.println("\n--------------------- classGeneric ---------------------");
  3. GenericClass gc = new GenericClass();
  4. Type[] gis = gc.getClass().getGenericInterfaces(); // 接口的泛型信息
  5. Type gps = gc.getClass().getGenericSuperclass(); // 父類的泛型信息
  6. TypeVariable[] gtr = gc.getClass().getTypeParameters(); // 當前接口的參數信息
  7. System.out.println("============== getGenericInterfaces");
  8. for (Type t : gis) {
  9. System.out.println(t + " : " + getClass(t, 0));
  10. }
  11. System.out.println("============== getGenericSuperclass");
  12. System.out.println(getClass(gps, 0));
  13. System.out.println("============== getTypeParameters");
  14. for (TypeVariable t : gtr) {
  15. StringBuilder stb = new StringBuilder();
  16. for (Type tp : t.getBounds()) {
  17. stb.append(tp + " : ");
  18. }
  19. System.out.println(t + " : " + t.getName() + " : " + stb);
  20. }
  21. }

針對method的泛型接口使用:

Java代碼

  1. private static void methodGeneric() throws Exception {
  2. System.out.println("\n--------------------- methodGeneric ---------------------");
  3. GenericClass gc = new GenericClass();
  4. Method method3 = gc.getClass().getDeclaredMethod("method3", new Class[] { Object.class });
  5. Type[] gpt3 = method3.getGenericParameterTypes();
  6. Type[] get3 = method3.getGenericExceptionTypes();
  7. Type gt3 = method3.getGenericReturnType();
  8. System.out.println("============== getGenericParameterTypes");
  9. for (Type t : gpt3) {
  10. System.out.println(t + " : " + getClass(t, 0));
  11. }
  12. System.out.println("============== getGenericExceptionTypes");
  13. for (Type t : get3) {
  14. System.out.println(t + " : " + getClass(t, 0));
  15. }
  16. System.out.println("============== getType");
  17. System.out.println(gt3 + " : " + getClass(gt3, 0));
  18. }

針對fIEld的泛型接口使用:

Java代碼

  1. private static void fIEldGeneric() throws Exception {
  2. System.out.println("\n--------------------- fIEldGeneric ---------------------");
  3. GenericClass gc = new GenericClass();
  4. Field field = gc.getClass().getSuperclass().getDeclaredFIEld("result");
  5. Type gt = fIEld.getGenericType();
  6. Type ft = fIEld.getType();
  7. System.out.println("============== getGenericType");
  8. System.out.println(gt + " : " + getClass(gt, 0));
  9. System.out.println("============== getType");
  10. System.out.println(ft + " : " + getClass(ft, 0));
  11. }

輸出結果:

Java代碼

  1. --------------------- classGeneric ---------------------
  2. ============== getGenericInterfaces
  3. com.agapple.misc.GeneircInteface> : interface Java.util.List
  4. interface com.agapple.misc.CommonInteface : interface com.agapple.misc.CommonInteface
  5. ============== getGenericSuperclass
  6. interface Java.util.List
  7. ============== getTypeParameters
  8. --------------------- fIEldGeneric ---------------------
  9. ============== getGenericType
  10. R : class Java.lang.Object
  11. ============== getType
  12. class Java.lang.Object : class Java.lang.Object
  13. --------------------- methodGeneric ---------------------
  14. ============== getGenericParameterTypes
  15. T : class Java.lang.Object
  16. ============== getGenericExceptionTypes
  17. E : class Java.lang.Throwable
  18. ============== getType
  19. T : class Java.lang.Object

結果說明:

因為泛型的擦拭,對應的GeneircInteface和BaseGeneircInteface,在源碼信息已被擦除對應的類型,進行了upper轉型,所以取到的是Object。可以使用extends

GenericClass在類定義時,聲明了繼承父接口的泛型為List,所以再通過接口和父類獲取泛型信息時,是能正確的獲取。通過Javap -v可以獲取對應的class信息

Java代碼

  1. const #46 = Asciz Lcom/agapple/misc/BaseGeneircInteface;>;Lcom/agapple/misc/GeneircInteface;>;Lcom/agapple/misc/CommonInteface;;

而在GenericClass中定義的方法method3,在class信息是一個被向上轉型後擦拭的信息。所以獲取method3的相關泛型信息是沒有的。

Java代碼

  1. method3;
  2. const #36 = Asciz (Ljava/lang/Object;)LJava/lang/Object;;
  3. const #37 = Asciz Exceptions;
  4. const #38 = class #39; // Java/lang/Throwable
  5. const #39 = Asciz Java/lang/Throwable;
  6. const #40 = Asciz (TT;)TT;^TE;;
  7. const #41 = Asciz TT;;

思考問題:

List list = new ArrayList(); 是否有獲取對應的String泛型信息? 不能,臨時變量不能保存泛型信息到具體class對象中,List和List對應的class實體是同一個。

Java代碼

  1. GeneircInteface gi = new GeneircInteface() {
  2. @Override
  3. public Integer method1(Integer obj) {
  4. return 1;
  5. }
  6. };

通過匿名類的方式,是否可以獲取Integer的泛型信息? 能,匿名類也會在進行class compiler保存泛型信息。

假如本文例子中的method3,是放在父類中BaseGeneircInteface中進行申明,GenericClass中指定R為List,是否可以獲取到對應的泛型信息? 不能,理由和問題1類似。

備注

具體泛型擦拭和信息保存,引用了撒迦的一段回復,解釋的挺詳盡了。

RednaxelaFX 寫道

Java泛型有這麼一種規律:

位於聲明一側的,源碼裡寫了什麼到運行時就能看到什麼;

位於使用一側的,源碼裡寫什麼到運行時都沒了。

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

Java代碼

  1. import Java.util.List;
  2. import Java.util.Map;
  3. public class GenericClass { // 1
  4. private List list; // 2
  5. private Map map; // 3
  6. public U genericMethod(Map m) { // 4
  7. return null;
  8. }
  9. }

上面代碼裡,帶有注釋的行裡的泛型信息在運行時都還能獲取到,原則是源碼裡寫了什麼運行時就能得到什麼。針對1的GenericClass,運行時通過Class.getTypeParameters()方法得到的數組可以獲取那個“T”;同理,2的T、3的Java.lang.String與T、4的T與U都可以獲得。

這是因為從Java 5開始class文件的格式有了調整,規定這些泛型信息要寫到class文件中。以上面的map為例,通過Javap來看它的元數據可以看到記錄了這樣的信息:

Javap代碼

  1. private Java.util.Map map;
  2. Signature: LJava/util/Map;
  3. Signature: length = 0x2
  4. 00 0A

乍一看,private Java.util.Map map;不正好顯示了它的泛型類型被擦除了麼?

但仔細看會發現有兩個Signature,下面的一個有兩字節的數據,0x0A。到常量池找到0x0A對應的項,是:

Javap代碼

  1. const #10 = Asciz LJava/util/Map;;

也就是內容為“LJava/util/Map;”的一個字符串。

根據Java 5開始的新class文件格式規范,方法與域的描述符增添了對泛型信息的記錄,用一對尖括號包圍泛型參數,其中普通的引用類型用“La/b/c/D;”的格式記錄,未綁定值的泛型變量用“Txxx;”的格式記錄,其中xxx就是源碼中聲明的泛型變量名。類型聲明的泛型信息也以類似下面的方式記了下來:

Javap代碼

  1. public class GenericClass extends Java.lang.Object
  2. Signature: length = 0x2
  3. 00 12
  4. // ...
  5. const #18 = Asciz LJava/lang/Object;;

詳細信息請參考官方文檔:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf

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

Java代碼

  1. import Java.util.ArrayList;
  2. import Java.util.List;
  3. public class TestClass {
  4. public static void main(String[] args) {
  5. List list = null; // 1
  6. list = new ArrayList(); // 2
  7. for (int i = 0; i < 10; i++) ;
  8. }
  9. }

上面代碼中,1留下的痕跡是:main()方法的StackMapTable屬性裡可以看到:

Java代碼

  1. StackMapTable: number_of_entrIEs = 2
  2. frame_type = 253 /* append */
  3. offset_delta = 12
  4. locals = [ class Java/util/List, int ]
  5. frame_type = 250 /* chop */
  6. offset_delta = 11

但這裡是沒有留下泛型信息的。這段代碼只所以寫了個空的for循環就是為了迫使Javac生成那個StackMapTable,讓1多留個影。

如果main()裡用到了list的方法,那麼那些方法調用點上也會留下1的痕跡,例如如果調用list.add("");,則會留下“java/util/List.add:(LJava/lang/Object;)Z”這種記錄。

2留下的是“Java/util/ArrayList."":()V”,同樣也丟失了泛型信息。

由上述討論可知,想對帶有未綁定的泛型變量的泛型類型獲取其實際類型是不現實的,因為class文件裡根本沒記錄實際類型的信息。覺得這句話太拗口的話用例子來理解:要想對Java.util.List獲取E的實際類型是不現實的,因為List.class文件裡只記錄了E,卻沒記錄使用List時E的實際類型。

想對局部變量等“使用一側”的已綁定的泛型類型獲取其實際類型也不現實,同樣是因為class文件中根本沒記錄這個信息。例子直接看上面講“使用一側”的就可以了。

知道了什麼信息有記錄,什麼信息沒有記錄之後,也就可以省點力氣不去糾結“拿不到T的實際類型”、“建不出T類型的數組”之類的問題了orz

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved