Java 获取运行时泛型类的class

首先:为什么不能使用简单的反射?

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

class A<T> {
	public void foo() {
		//如何在此处获得运行时 T 的具体类型呢?
	}
}

设想我们使用时

new A<String>().foo();

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

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

在这里插入图片描述

或者用 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 泛型机制是如何擦除类型的,我们接下来的问题就是如何通过反射获得泛型签名中的类型,一般会在继承或实现泛型接口时会用到它。

实现方法

1.继承类

class A<T, ID> { 
} 

class B extends A<String, Integer> {
} 

public class Generic { 
	public static void main(String[] args) {
		 System.out.println(B.class.getGenericSuperclass()); 
	} 
}

上面的代码输出是

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

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

在这里插入图片描述

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

ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass();

Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

for(Type actualTypeArgument: actualTypeArguments) {
    System.out.println(actualTypeArgument);
}

上面的代码输出

class java.lang.String
class java.lang.Integer

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

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

2.实现接口

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

interface A<T, ID> {
}

class B implements A<String, Integer> {
}

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

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

ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericInterfaces()[0];

Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

for (Type actualTypeArgument : actualTypeArguments) {
    System.out.println(actualTypeArgument);
}

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

总结一下

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

Java 获取当前类的泛型class java运行时获取泛型的类型

转自:https://blog.51cto.com/u_12187/6507441

Java获取泛型T的Class通常有两种方式。 第一种方式是使用泛型类的getClass()方法来获取泛型T的Class信息。这种方式的前提是必须要创建泛型对象。具体实现方法如下: ``` public class GenericClass<T>{ public Class<T> getGenericClass() { Class<T> cls = (Class<T>) ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]; return cls; } } public class Main { public static void main(String[] args) { GenericClass<String> gc = new GenericClass<String>(){}; System.out.println(gc.getGenericClass()); } } ``` 第二种方式是使用TypeReference。这是由Jackson库提供的一个工具,可以避免在运行时由于泛型擦除而导致的Class信息丢失的问题。具体实现方法如下: ``` public abstract class TypeReference<T> { private final Type type; protected TypeReference() { Type superClass = getClass().getGenericSuperclass(); if (superClass instanceof Class<?>) { throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information"); } type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; } public Type getType() { return type; } } public class Main { public static void main(String[] args) { TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {}; System.out.println(typeReference.getType()); } } ``` 无论是哪种方式,获取泛型T的Class都可以方便地进行型检查和型转换操作。但需要注意的是,在获取泛型T的Class时,必须要保证T已经被实例化。如果T没有被实例化,则获取Class信息可能不准确,无法保证正确性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值