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

末蓝、 2021-09-15 04:30 393阅读 0赞

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

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

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

[java] view plain copy

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

设想我们使用时

[java] view plain copy

  1. new A().foo();

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

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

Center

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

Signature: #17 // Ljava/lang/Object;

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

class A {

}

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

Signature: #18 // Ljava/lang/Object;

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

public void foo(T t) {

t.getClass();

}

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

继承一个泛型基类

[java] view plain copy

  1. class A {
  2. }
  3. class B extends A {
  4. }
  5. public class Generic {
  6. public static void main(String[] args) {
  7. System.out.println(B.class.getGenericSuperclass());
  8. }
  9. }

上面的代码输出是

cc.unmi.A

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

Center 1

这张图可以看到 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;

实现一个泛型接口

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

[java] view plain copy

  1. interface A {
  2. }
  3. class B implements A {
  4. }

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

Center 2

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

[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 来查看泛型签名来找到线索

example:

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

ClassA类:

[java] view plain copy

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

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. public class Test extends ClassA {
  6. private List list;
  7. private Map map;
  8. /***
  9. * 获取List中的泛型
  10. */
  11. public static void testList() throws NoSuchFieldException, SecurityException {
  12. Type t = Test.class.getDeclaredField(“list”).getGenericType();
  13. if (ParameterizedType.class.isAssignableFrom(t.getClass())) {
  14. for (Type t1 : ((ParameterizedType) t).getActualTypeArguments()) {
  15. System.out.print(t1 + “,”);
  16. }
  17. System.out.println();
  18. }
  19. }
  20. /***
  21. * 获取Map中的泛型
  22. */
  23. public static void testMap() throws NoSuchFieldException, SecurityException {
  24. Type t = Test.class.getDeclaredField(“map”).getGenericType();
  25. if (ParameterizedType.class.isAssignableFrom(t.getClass())) {
  26. for (Type t1 : ((ParameterizedType) t).getActualTypeArguments()) {
  27. System.out.print(t1 + “,”);
  28. }
  29. System.out.println();
  30. }
  31. }
  32. public static void main(String args[]) throws Exception {
  33. System.out.println(“>>>>>>>>>>>testList>>>>>>>>>>>”);
  34. testList();
  35. System.out.println(“<<<<<<<<<<<testList<<<<<<<<<<<\n”);
  36. System.out.println(“>>>>>>>>>>>testMap>>>>>>>>>>>”);
  37. testMap();
  38. System.out.println(“<<<<<<<<<<<testMap<<<<<<<<<<<\n”);
  39. System.out.println(“>>>>>>>>>>>testClassA>>>>>>>>>>>”);
  40. new Test().testClassA();
  41. System.out.println(“<<<<<<<<<<<testClassA<<<<<<<<<<<”);
  42. }
  43. }

结果:

[plain] view plain copy

  1. testList>>>>>>>>>>>

  2. class java.lang.String,
  3. <<<<<<<<<<<testList<<<<<<<<<<<
  4. testMap>>>>>>>>>>>

  5. class java.lang.String,class java.lang.Object,
  6. <<<<<<<<<<<testMap<<<<<<<<<<<
  7. testClassA>>>>>>>>>>>

  8. getSuperclass:com.pelin.util.ClassA
  9. getGenericSuperclass:com.pelin.util.ClassA
  10. getActualTypeArguments:class java.lang.String,
  11. <<<<<<<<<<<testClassA<<<<<<<<<<<

发表评论

表情:
评论列表 (有 0 条评论,393人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java运行获得类型

    引言 众所周知,java泛型最重要的特征是泛型擦除,所有泛型在编译时会转换成Object所以在java中运行时无法获得泛型的类型。 但是其实以上的规则是针对方法的内部