打个赌你可能不知道如何获取Java泛型的Class对象

Java中的泛型有着很重要的作用,它能够让我们的数据容器类型安全,避免发生转换异常。不过Java中的泛型也为人诟病,它会在编译中被全部转换成Object对象,也就是泛型擦除,这造成了诸多不便,除非你能获取泛型的一个实例,否则我们无法直接获取泛型的实际类型。不过JDK依然提供了一个技巧让我们可以获得泛型的具体类型。

大致原理虽然泛型会在字节码编译过程中被擦除,但是Class对象会通过java.lang.reflect.Type记录其实现的接口和继承的父类信息。我们以ArrayList为例:

代码语言:javascript代码运行次数:0运行复制 ArrayList strings = new ArrayList<>();

Type genericSuperclass = strings.getClass().getGenericSuperclass();

// genericInterfaces = java.util.AbstractList

System.out.println("genericSuperclass = " + genericSuperclass);

虽然我们成功打印出来了泛型的占位符E,但是这并不是我们想要的。我们希望能够获取E的具体类型,也就是String。

让我们回到java.lang.reflect.Type

Type的实现类型

通过上图可以知道Type有四种类型:

GenericArrayType 用来描述一个参数泛型化的数组。WildcardType 用来描述通配符?相关的泛型,包含的?、下界通配符? super E 、上界通配符? extend E。Class 用来描述类的Class对象。ParameterizedType 用来描述参数化类型。我们再来试一试:

代码语言:javascript代码运行次数:0运行复制ArrayList 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

我们来看看参数化类型能不能获取到具体的类型:

代码语言:javascript代码运行次数:0运行复制 ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;

Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

// [E]

System.out.println("actualTypeArguments = " + Arrays.toString(actualTypeArguments));

ParameterizedType可以帮助我们获取参数类型,可惜依然是E。两种方法为什么都只能获取一个泛型占位符呢?

代码语言:javascript代码运行次数:0运行复制public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable

{

// 省略

}

这是因为ArrayList实例化时只指定了自己的泛型类型而没有指定父类AbstractList的具体泛型,所以获取到的就是占位符E。我们先来看一个抽象类例子:

代码语言:javascript代码运行次数:0运行复制 public abstract class SuperClass {

private E e;

protected SuperClass(E e) {

this.e = e;

}

public E get() {

return this.e;

}

}

构建匿名子类实现:

代码语言:javascript代码运行次数:0运行复制// 实现

SuperClass superClassInstance = new SuperClass("试一试") {

};

Type genericSuperclass1 = superClassInstance.getClass().getGenericSuperclass();

//SuperClass

System.out.println(genericSuperclass1);

我们通过大括号{}就可以重写实现父类的方法并指定父类的泛型具体类型。我们可以借助这一特性来获取父类的具体泛型类型,我们还拿ArrayList来试试:

代码语言:javascript代码运行次数:0运行复制ArrayList strings = new ArrayList(){};

Type genericSuperclass = strings.getClass().getGenericSuperclass();

// genericSuperclass = java.util.ArrayList

System.out.println("genericSuperclass = " + genericSuperclass);

证明了我们的猜想是对的。那么问题来了如何封装一个工具类?

封装工具类我们可以借助于抽象类来定义一个获取java.lang.reflect.ParameterizedType的工具类。好在Spring框架中已经提供了一个很好用的实现:

代码语言:javascript代码运行次数:0运行复制public abstract class ParameterizedTypeReference {

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都有类似的实现。

所以今天你又学了一招,而且这一招相当的有创意。这一招在封装一些通用类库的时候非常有用,比如反序列化工具类。看完了别忘关注码农小胖哥并一键四连哦。