通过上一篇Java反射之表示所有类型的Type接口一文,我们知道 Type 接口及其五大组件是用来描述Java的各种类型,主要包括原始类型和泛型类型。很多时候,我们需要获取到泛型类上定义的具体类型,从而完成一些业务逻辑。比如,最常见的情景就是JSON的反序列化,需要将JSON字符串反序列化为泛型的具体类型,比如反序列化为 List<User>,这就要求每一个 List 中的元素都是 User 对象。那么,如何获取到泛型类上定义的具体类型呢?这就是本文要阐述的内容。

1. 通过Class能获取泛型类型吗?

/2021/11/09/reflect-class.html中说过,Class 类上有一个 getTypeParameters() 方法用来获取类上的类型变量。通过它能不能获取到泛型类型呢?看下边的示例:

class Parent<T> { (1)
}
1定义一个泛型类

这里定义一个泛型类,现在尝试通过它的实例对象来获取泛型类型,代码如下:

public void printParent() {
    Parent<String> parent = new Parent<>(); (1)
    TypeVariable<? extends Class<? extends Parent>>[] parentTypeParameters = parent.getClass().getTypeParameters(); (2)
    System.out.println(Arrays.toString(parentTypeParameters));
}
1创建 Parent 类的实例
2获取类型变量

上边的示例并不能拿到实例上的 String 类型,而只能拿到 Parent 类上定义的泛型类型 T,因此,输出结果为:

[T]

很显然,这个结果不是我们想要的,我们想要获取到 String 这个类。在 Java反射之创建泛型数组 一文中说过,泛型是编译期特性,在运行时泛型会被擦除。因此,我们想通过`Class` 在运行时用反射直接去获取泛型类型,看起来是不行的。

既然直接获取不行,那么有没有别的办法呢?

在上一篇Java反射之表示所有类型的Type接口一文中说过,Type 的参数化类型组件 ParameterizedType 可以获取整个泛型定义,那么要获取类上的泛型,肯定少不了它

但是,Field 通过 getGenericType() 方法可以获取到参数化类型,ConstructorMethod 可以通过 getGenericParameterTypes() 获取泛型参数或者通过 getGenericReturnType() 方法获取泛型值的泛型类型,而翻看 Class 类的 api,与泛型相关的看似有用的只有两个方法:

public Type[] getGenericInterfaces(); (1)
public Type getGenericSuperclass(); (2)
1获取类上的实现的泛型接口
2获取类的泛型父类

它们是不是我们所需要的呢?看下边的示例:

public class GenericActualType {
    interface GenericInterface1<T> { (1)
    }

    interface GenericInterface2<T> { (1)
    }

    class GenericSuperClass<T> { (2)
    }

    class GenericCls extends GenericSuperClass<Integer> implements GenericInterface1<String>, GenericInterface2<Long> { (3)
    }

    public void genericSuperClassAndInterface() { (4)
        Type genericSuperclass = GenericCls.class.getGenericSuperclass();
        Type[] genericInterfaces = GenericCls.class.getGenericInterfaces();
        System.out.println(genericSuperclass.getClass());
        for (Type genericInterface : genericInterfaces) {
            System.out.println(genericInterface.getClass());
        }
    }
}
1创建泛型接口
2创建泛型父类
3创建泛型类分别继承和实现了泛型父类和接口
4测试方法

示例中,标记1、2分别申明了两个泛型接口和一个泛型类,然后声明一个 GenericCls 类来继承和实现它们。最后,通过测试方法,打印出 getGenericSuperclass()getGenericInterfaces() 获取到的泛型类型,其结果输出如下:

class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl

可以看到,通过这两个方法,确实能够拿到参数化类型 ParameterizedType,然后通过它来获取泛型具体类型就很容易了,前一篇已经介绍过,不再赘述。

2. 为什么只能通过父类或接口来获取到子类的泛型?

其实这个问题是Java类体系决定的。

首先,泛型类在实例化时才知道具体类型,但是泛型类型被擦除,所以反射获取不到具体类型,只能获取到泛型类申明时定义的诸如 TE 之类的东西:

Parent<String> parent = new Parent<>();

其次,子类继承或实现泛型父类或接口,就获得了具体的泛型定义,如下所示:

class GenericCls extends GenericSuperClass<Integer> {
}

子类 GenericCls 在编译期和运行期都知道其泛型类型是 Integer,这个泛型类型已经具体化了,所以能够获取到具体泛型类型。

所以,有的书上说Java中的泛型并不是真正意义的泛型。

3. 通用的获取类定义的具体泛型方案

有了前边的了解,现在我们可以得到一个思路:要获取泛型类的具体泛型类型,需要创建其子类并持有具体类型。看一个例子:

// List<String> list = new ArrayList<String>(); (1)
List<String> list = new ArrayList<String>() {
}; (2)
Type genericSuperclass = list.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
System.out.println(Arrays.toString(actualTypeArguments));
1不能获取的泛型类型
2通过创建 ArrayList 的匿名子类来获取泛型类型

注意标记1和2的区别,2是创建了 ArrayList 的匿名子类,从而通过 getGenericSuperclass() 就能获取泛型真实类型。上边的示例输出:

[class java.lang.String]

这种方式虽然有用,但是不够优雅,可以编写一个专门负责泛型类型具体化的抽象父类,如下所示:

abstract class ParameterizedTypeRef<T> {
    private Type type; (1)

    public ParameterizedTypeRef() {
        Class<?> cls = findParameterizedTypeClass(this.getClass()); (2)
        Type genericSuperclass = cls.getGenericSuperclass();
        assert genericSuperclass instanceof ParameterizedType;
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        this.type = parameterizedType.getActualTypeArguments()[0]; (3)
    }

    public Type getType() { (4)
        return type;
    }

    private static Class<?> findParameterizedTypeClass(Class<?> child) {
        Class<?> parent = child.getSuperclass();
        if (parent == Object.class) // 没找到,抛异常
            throw new IllegalArgumentException();
        if (parent == ParameterizedTypeRef.class) // 找到,直接返回
            return child;
        return findParameterizedTypeClass(parent); // 递归查找
    }
}
1持有泛型具体类型的成员域
2找到父类是ParameterizedTypeRef的类,从而根据父类可以获取其泛型类型
3通过参数化类型获取具体泛型类型
4返回具体泛型类型

然后,其使用方法如下:

ParameterizedTypeRef<String> parameterizedTypeRef = new ParameterizedTypeRef<String>() {
};
System.out.println(parameterizedTypeRef.getType()); // class java.lang.String
ParameterizedTypeRef<Integer> intParameterizedTypeRef = new ParameterizedTypeRef<Integer>() {
};
System.out.println(intParameterizedTypeRef.getType());
ParameterizedTypeRef<Parent<String>> parentParameterizedTypeRef = new ParameterizedTypeRef<Parent<String>>() {
};
System.out.println(parentParameterizedTypeRef.getType().getTypeName()); // com.belonk.lang.generic.Parent<java.lang.String>

上边的示例通过创建 ParameterizedTypeRef 的匿名子类来具体化泛型参数。

其实,很多框架都是采用这种方式来获取具体泛型的:

1、Spring中有一个 ParameterizedTypeReference 类,来做同样的事情:

public abstract class ParameterizedTypeReference<T> {

    private final Type type;


    protected ParameterizedTypeReference() {
        Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
        Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
        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];
    }

    // 省略其他代码
}

2、Jackson中的解决方案

Jackson在反序列化是,可以通过传递 TypeReference 对象的匿名子类来决定底层json需要反序列化的对象,比如:

List<User> users = objectMapper.readValue(json, new TypeReference<List<User>>() {});

示例中,使用 ObjectMapper 直接将json反序列化 List<User> 类型的对象,list中每一个元素都是 User,其实就是通过 TypeReference 来持有具体泛型实现的,TypeReference 定义如下:

public abstract class TypeReference<T> implements Comparable<TypeReference<T>> {
    protected final Type _type;

    protected TypeReference() {
        Type superClass = this.getClass().getGenericSuperclass();
        if (superClass instanceof Class) {
            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
        } else {
            this._type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
        }
    }

    public Type getType() {
        return this._type;
    }

    public int compareTo(TypeReference<T> o) {
        return 0;
    }
}

4. 总结

Java不能通过 Class 直接获取到类的具体泛型,但是可以通过创建泛型父类持有具体泛型并创建其匿名子类来实现。

本文示例代码见: github.


相关阅读