Java 运行时如何获取泛型参数的类型

https://blog.****.net/hj7jay/article/details/54889717

https://blog.****.net/xiaozaq/article/details/52329321

在 Java 中对于下面最简单的泛型类

[java] view plain copy
  1. class A<T> {  
  2.     public void foo() {  
  3.         //如何在此处获得运行时 T 的具体类型呢?  
  4.     }  
  5. }  

设想我们使用时

[java] view plain copy
  1. new A<String>().foo();  

是否能在 foo() 方法中获得当前的类型是 String 呢?答案是否定的,不能。在 foo() 方法中 this 引用给不出类型信息, this.getClass() 就更不可能了,因为 Java 的泛型不等同于 C++ 的模板类, this.getClass() 实例例是被所有的不同具体类型的 A 实例(new A<String>(), new A<Integer>() 等) 共享的,所以在字节码中类型会被擦除到上限。

我们可以在 IDE 的调试时看到这个泛型类的签名

Java 运行时如何获取泛型参数的类型

或者用 javap -v cc.unmi.A 可以查看到类 A 的泛型签名

Signature: #17                          // <T:Ljava/lang/Object;>Ljava/lang/Object;

为什么说是擦除到上限呢?并不是泛型在字节码中都表示为 Object , 看下面的例子,假如 A 声明如下

class A<T extends Number> {

}

再用 javap -v cc.unmi.A 来看泛型签名

Signature: #18                          // <T:Ljava/lang/Number;>Ljava/lang/Object;

也就是说在上面的 foo() 方法中无法获得当前的类型,我们必须给它加个参数 T

public void foo(T t) {

t.getClass();

}

了解了 Java 泛型机制是如何擦除类型的,我们接下来的问题就是如何通过反射获得泛型签名中的类型,一般会在继承或实现泛型接口时会用到它。

继承一个泛型基类

[java] view plain copy
  1. class A<T, ID> {  
  2. }  
  3.   
  4. class B extends A<String, Integer> {  
  5. }  
  6.   
  7. public class Generic {  
  8.   
  9.     public static void main(String[] args) {  
  10.         System.out.println(B.class.getGenericSuperclass());  
  11.     }  
  12. }  

上面的代码输出是

cc.unmi.A<java.lang.String, java.lang.Integer>

所以要获得这两个类型是可行的,设置了断点

Java 运行时如何获取泛型参数的类型

这张图可以看到 B.class.getGenericSuperclass() 得到的实际类型是 ParameterizedTypeImpl 通过它就可以获得 actualTypeArguments 了。代码就是

[java] view plain copy
  1. ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass();  
  2. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  3. for(Type actualTypeArgument: actualTypeArguments) {  
  4.     System.out.println(actualTypeArgument);  
  5. }  

上面的代码输出

class java.lang.String

class java.lang.Integer

我们不妨用 javap -v cc.unmi.B 的泛型签名

Signature: #12                          // Lcc/unmi/A<Ljava/lang/String;Ljava/lang/Integer;>;

实现一个泛型接口

这时与继承一个泛型基类的情况略有不同,如下关系,A 是一个泛型接口

[java] view plain copy
  1. interface A<T, ID> {  
  2. }  
  3.   
  4. class B implements A<String, Integer> {  
  5. }  

该如何反射获得 B 的参数类型呢,用上面的方法已不可行, B.class.getGenericSuperclass() 已不是一个 ParameterizedTypeImpl 而是一个 Object 类型。现在需要另一个方法 getGenericInterfaces(): Type[] 它得到一个 Type 数组,代表类实现的多个接口,因为我们这儿只实现了一个接口,所以取第一个元素,它的类型是我们已见过的 ParameterizedTypeImpl ,

Java 运行时如何获取泛型参数的类型

因此我们用来获得实现接口而来的泛型参数的代码就是

[java] view plain copy
  1. ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces()[0];  
  2. Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
  3. for (Type actualTypeArgument : actualTypeArguments) {  
  4.     System.out.println(actualTypeArgument);  
  5. }  

同样能得到上面的一样的结果。

总结一下

  1. 如果是继承基类而来的泛型,就用 getGenericSuperclass() , 转型为 ParameterizedType 来获得实际类型
  2. 如果是实现接口而来的泛型,就用 getGenericInterfaces() , 针对其中的元素转型为 ParameterizedType 来获得实际类型
  3. 我们所说的 Java 泛型在字节码中会被擦除,并不总是擦除为 Object 类型,而是擦除到上限类型
  4. 能否获得想要的类型可以在 IDE 中,或用 javap -v <your_class>   来查看泛型签名来找到线索

example:

获取几种形式泛型的Class类型:

ClassA类:

[java] view plain copy
  1. import java.lang.reflect.ParameterizedType;  
  2. import java.lang.reflect.Type;  
  3.   
  4. public class  ClassA <T>{         
  5.     private T obj;         
  6.     public void setObject(T obj) {      this.obj = obj;  }       
  7.     public T getObject() {    return obj;   }     
  8.       
  9.     /** 
  10.      * 获取T的实际类型 
  11.      */  
  12.     public void testClassA() throws NoSuchFieldException, SecurityException {  
  13.         System.out.print("getSuperclass:");  
  14.         System.out.println(this.getClass().getSuperclass().getName());  
  15.         System.out.print("getGenericSuperclass:");  
  16.         Type t = this.getClass().getGenericSuperclass();  
  17.         System.out.println(t);  
  18.         if (ParameterizedType.class.isAssignableFrom(t.getClass())) {  
  19.             System.out.print("getActualTypeArguments:");  
  20.             for (Type t1 : ((ParameterizedType) t).getActualTypeArguments()) {  
  21.                 System.out.print(t1 + ",");  
  22.             }  
  23.             System.out.println();  
  24.         }  
  25.     }  
  26. }      

Test类:

[java] view plain copy
  1. import java.lang.reflect.Type;  
  2. import java.util.List;  
  3. import java.util.Map;  
  4. import java.lang.reflect.ParameterizedType;  
  5.   
  6. public class Test extends ClassA<String> {  
  7.     private List<String> list;  
  8.     private Map<String, Object> map;  
  9.   
  10.     /*** 
  11.      * 获取List中的泛型 
  12.      */  
  13.     public static void testList() throws NoSuchFieldException, SecurityException {  
  14.         Type t = Test.class.getDeclaredField("list").getGenericType();  
  15.         if (ParameterizedType.class.isAssignableFrom(t.getClass())) {  
  16.             for (Type t1 : ((ParameterizedType) t).getActualTypeArguments()) {  
  17.                 System.out.print(t1 + ",");  
  18.             }  
  19.             System.out.println();  
  20.         }  
  21.     }  
  22.   
  23.     /*** 
  24.      * 获取Map中的泛型 
  25.      */  
  26.     public static void testMap() throws NoSuchFieldException, SecurityException {  
  27.         Type t = Test.class.getDeclaredField("map").getGenericType();  
  28.         if (ParameterizedType.class.isAssignableFrom(t.getClass())) {  
  29.             for (Type t1 : ((ParameterizedType) t).getActualTypeArguments()) {  
  30.                 System.out.print(t1 + ",");  
  31.             }  
  32.             System.out.println();  
  33.         }  
  34.     }  
  35.   
  36.     public static void main(String args[]) throws Exception {  
  37.         System.out.println(">>>>>>>>>>>testList>>>>>>>>>>>");  
  38.         testList();  
  39.         System.out.println("<<<<<<<<<<<testList<<<<<<<<<<<\n");  
  40.         System.out.println(">>>>>>>>>>>testMap>>>>>>>>>>>");  
  41.         testMap();  
  42.         System.out.println("<<<<<<<<<<<testMap<<<<<<<<<<<\n");  
  43.         System.out.println(">>>>>>>>>>>testClassA>>>>>>>>>>>");  
  44.         new Test().testClassA();  
  45.         System.out.println("<<<<<<<<<<<testClassA<<<<<<<<<<<");  
  46.     }  
  47.   
  48. }  

结果:

[plain] view plain copy
  1. >>>>>>>>>>>testList>>>>>>>>>>>  
  2. class java.lang.String,  
  3. <<<<<<<<<<<testList<<<<<<<<<<<  
  4.   
  5. >>>>>>>>>>>testMap>>>>>>>>>>>  
  6. class java.lang.String,class java.lang.Object,  
  7. <<<<<<<<<<<testMap<<<<<<<<<<<  
  8.   
  9. >>>>>>>>>>>testClassA>>>>>>>>>>>  
  10. getSuperclass:com.pelin.util.ClassA  
  11. getGenericSuperclass:com.pelin.util.ClassA<java.lang.String>  
  12. getActualTypeArguments:class java.lang.String,  
  13. <<<<<<<<<<<testClassA<<<<<<<<<<<