打个赌你可能不知道如何获取Java泛型的Class对象
Java中的泛型有着很重要的作用,它能够让我们的数据容器类型安全,避免发生转换异常。不过Java中的泛型也为人诟病,它会在编译中被全部转换成Object
对象,也就是泛型擦除,这造成了诸多不便,除非你能获取泛型的一个实例,否则我们无法直接获取泛型的实际类型。不过JDK依然提供了一个技巧让我们可以获得泛型的具体类型。
大致原理
虽然泛型会在字节码编译过程中被擦除,但是Class
对象会通过java.lang.reflect.Type
记录其实现的接口和继承的父类信息。我们以ArrayList<E>
为例:
ArrayList<String> strings = new ArrayList<>();
Type genericSuperclass = strings.getClass().getGenericSuperclass();
// genericInterfaces = java.util.AbstractList<E>
System.out.println("genericSuperclass = " + genericSuperclass);
虽然我们成功打印出来了泛型的占位符E
,但是这并不是我们想要的。我们希望能够获取E
的具体类型,也就是String
。
让我们回到java.lang.reflect.Type
通过上图可以知道Type
有四种类型:
GenericArrayType
用来描述一个参数泛型化的数组。WildcardType
用来描述通配符?
相关的泛型,包含的?
、下界通配符? super E
、上界通配符? extend E
。Class<T>
用来描述类的Class
对象。ParameterizedType
用来描述参数化类型。
我们再来试一试:
ArrayList<String> strings = new ArrayList<>();
Type genericSuperclass = strings.getClass().getGenericSuperclass();
System.out.println( genericSuperclass instanceof ParameterizedType); // true
System.out.println( genericSuperclass instanceof Class); // false
System.out.println( genericSuperclass instanceof WildcardType); // false
System.out.println( genericSuperclass instanceof GenericArrayType); // false
我们来看看参数化类型能不能获取到具体的类型:
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// [E]
System.out.println("actualTypeArguments = " + Arrays.toString(actualTypeArguments));
ParameterizedType
可以帮助我们获取参数类型,可惜依然是E
。两种方法为什么都只能获取一个泛型占位符呢?
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 省略
}
这是因为ArrayList
实例化时只指定了自己的泛型类型而没有指定父类AbstractList
的具体泛型,所以获取到的就是占位符E
。我们先来看一个抽象类例子:
public abstract class SuperClass<E> {
private E e;
protected SuperClass(E e) {
this.e = e;
}
public E get() {
return this.e;
}
}
构建匿名子类实现:
// 实现
SuperClass<String> superClassInstance = new SuperClass<String>("试一试") {
};
Type genericSuperclass1 = superClassInstance.getClass().getGenericSuperclass();
//SuperClass<java.lang.String>
System.out.println(genericSuperclass1);
我们通过大括号{}
就可以重写实现父类的方法并指定父类的泛型具体类型。我们可以借助这一特性来获取父类的具体泛型类型,我们还拿ArrayList
来试试:
ArrayList<String> strings = new ArrayList<String>(){};
Type genericSuperclass = strings.getClass().getGenericSuperclass();
// genericSuperclass = java.util.ArrayList<java.lang.String>
System.out.println("genericSuperclass = " + genericSuperclass);
证明了我们的猜想是对的。那么问题来了如何封装一个工具类?
封装工具类
我们可以借助于抽象类来定义一个获取java.lang.reflect.ParameterizedType
的工具类。好在Spring框架中已经提供了一个很好用的实现:
public abstract class ParameterizedTypeReference<T> {
private final Type type;
protected ParameterizedTypeReference() {
Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(this.getClass());
// 获取父类的泛型类 ParameterizedTypeReference<具体类型>
Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
// 必须是 ParameterizedType
Assert.isInstanceOf(ParameterizedType.class, type, "Type must be a parameterized type");
ParameterizedType parameterizedType = (ParameterizedType)type;
// 获取泛型的具体类型 这里是单泛型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Assert.isTrue(actualTypeArguments.length == 1, "Number of type arguments must be 1"); // 设置结果
this.type = actualTypeArguments[0];
}
// 这个方法开放出去用来调用 来获取泛型的具体Class类型
public Type getType() {
return this.type;
}
private static Class<?> findParameterizedTypeReferenceSubclass(Class<?> child) {
Class<?> parent = child.getSuperclass();
// 如果父类是Object 就没戏了
if (Object.class == parent) {
throw new IllegalStateException("Expected ParameterizedTypeReference superclass");
} else {
// 如果父类是工具类本身 就返回否则就递归 直到获取到ParameterizedTypeReference
return ParameterizedTypeReference.class == parent ? child : findParameterizedTypeReferenceSubclass(parent);
}
}
}
❝其实 Jackson 、 Gson都有类似的实现。
所以今天你又学了一招,而且这一招相当的有创意。这一招在封装一些通用类库的时候非常有用,比如反序列化工具类。看完了别忘关注码农小胖哥并一键四连哦。
2021-06-30
2021-06-25