运行时获取泛型类型

由于整个CoreLibs中选用的序列化/反序列化工具是GSON, 因此很多时候我们都需要知道泛型的具体类型是什么. 特别是在搭建框架的时候, 完全无法确定泛型的真正类型, 这时候就需要在运行时获取泛型类型, 并将类型传递给GSON. 如果获取不正确, GSON是无法进行反序列化的.

在Java中, 要想在运行时获取泛型类型不是一件简单的事情. 为什么呢? 这里首先得了解一下Java泛型的机制:

引用自 Java深度历险——Java泛型

正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。

很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:

  • 泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class

  • 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 > MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。

  • 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>MyException<Integer>的。对于JVM来说,它们都是MyException类型的。也就无法执行与异常对应的catch语句。

由于JVM执行的是.class文件,而不是.java文件,在JVM看来,这个class文件里没有任何关于T的信息,因此T.class是无法通过编译的. 那么Gson是怎么获取泛型类型的呢?

研究GSON官方文档发现, GSON在反序列化带有泛型的实体时, 建议我们使用如下方法:

Type type = new TypeToken<List<Data>>() {}.getType();
List<Data> list = gson.fromJson(json, type);

通过TypeToken这个类, Gson就能获取TypeToken中声明的泛型的实际类型, 在上述代码中就是List<Data>. TypeToken内部究竟是如何实现的呢? 主体上就是通过下列代码:

Type mySuperClass = getClass().getGenericSuperclass(); 
Type type = ((ParameterizedType) mySuperClass).getActualTypeArguments()[0];

通过getGenericSuperclass()来获取ParameterizedType, 并通过ParameterizedType的getActualTypeArguments()来获取泛型的类型. 但是最关键的还是getClass()调用的时机. getClass()方法是在TypeToken类内部调用的, 也就意味着getClass()返回的就是TypeToken的class. 接下来我们做个小实验:

Test<Data> data = new Test<>();
Type mySuperClass = data.getClass().getGenericSuperclass(); 
Type type = ((ParameterizedType) mySuperClass).getActualTypeArguments()[0];
System.out.println(type);

输出结果:

Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
    at com.ryan.corelibstest.view.test.Test.main(Test.java:15)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Process finished with exit code 1

可以看到报错了, mySuperClass根本就不是ParameterizedType. ParameterizedType到底是什么呢?

简单来说, ParameterizedType表示一种参数化的类型,比如Collection, 我可以通过ParameterizedType获取参数化类型<>中的实际类型

由于mySuperClass不是ParameterizedType类型, 因此我们无法通过此种方式获取泛型的实际类型. 但是换一种写法却会有不一样的结果:

Test<Data> data = new Test<Data>() {};
Type mySuperClass = data.getClass().getGenericSuperclass();
Type type = ((ParameterizedType)mySuperClass).getActualTypeArguments()[0];
System.out.println(type);

输出结果:

class com.ryan.corelibstest.view.test.Data

Process finished with exit code 0

这个时候就能获取到Data的类型了. 第二种写法的区别和第一种很小, 只是在声明Test<Data> data的时候在括号后加了一对大括号: new Test<Data>() {};. 这又是为什么呢? new Test<Data>() {};这句话意味着data的类型是类型Text<T>的匿名子类. 该匿名子类指定了T的类型为Data.

这里虽然通过另一种方式获得了匿名子类的Class类型,但是并没有直接将泛型参数T的Class类型传进来,那又是如何获得泛型参数的类型的呢, 这要依赖Java的Class字节码中存储的泛型参数信息。Java的泛型机制虽然在运行期间泛型类和非泛型类都相同,但是在编译java源代码成 class文件中还是保存了泛型相关的信息,这些信息被保存在class字节码常量池中,使用了泛型的代码处会生成一个signature签名字段,通过 签名signature字段指明这个常量池的地址。

了解了上述知识后发现, 我们还是必须指定子类的类型, 让JVM知道到底我们的T是什么类型. 这意味着我们还是不能在程序运行中去获取泛型的实际类型. 这个时候我们就必须放弃这种方式, 而从ParameterizedType接口入手.

首先我们来看看上面代码中的实体类的定义:

public class Test<T> implements Serializable {
    public T data;
}

public class Data implements Serializable {
    public int id;
}

ParameterizedType中总共有三个抽象方法, 其中两个对我们有用. 这里以Test<Data>举例:

  • Type getRawType() 获取声明类型, 在这里, rawType就是Test

  • Type[] getActualTypeArguments() 获取实际类型, 返回的是个数组, 在这里actualTypeArguments就是[Data].

因此, 我们可以通过ParameterizedType来告诉GSON我们想要的到底是什么类型. 在CoreLibs中的util包下的ParameterizedTypeImpl就是实现了ParameterizedType以用来获取泛型实际类型的工具类. 使用方法:

ParameterizedType type = ParameterizedTypeImpl.get(Test.class, Data.class);
Test<Data> test = gson.fromJson(json, type);

如果泛型的层级很多, 可以以此类推:

ParameterizedType type = ParameterizedTypeImpl.get(Test.class, List.class, Data.class);
Test<List<Data>> tests = gson.fromJson(json, type);

ParameterizedTypeImpl源码:

public class ParameterizedTypeImpl implements ParameterizedType {

    private final Type rawType;
    private final Type[] typeArguments;

    public static ParameterizedTypeImpl get(Type rawType, Type... typeArguments) {
        return new ParameterizedTypeImpl(rawType, typeArguments);
    }

    private ParameterizedTypeImpl(Type rawType, Type... typeArguments) {
        this.rawType = rawType;
        this.typeArguments = canonicalize(typeArguments.clone());
    }

    private Type[] canonicalize(Type[] typeArguments) {
        if (typeArguments != null && typeArguments.length > 1) {
            Type[] types = new Type[typeArguments.length - 1];
            for (int i = 0; i < typeArguments.length; i++) {
                if (i > 0) {
                    types[i - 1] = typeArguments[i];
                }
            }

            return new Type[] { new ParameterizedTypeImpl(typeArguments[0], types) };
        }

        return typeArguments;
    }

    @Override
    public Type[] getActualTypeArguments() {
        return typeArguments.clone();
    }

    @Override
    public Type getOwnerType() {
        return null;
    }

    @Override
    public Type getRawType() {
        return rawType;
    }

    public String typeToString(Type type) {
        return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder(30 * (typeArguments.length + 1));
        stringBuilder.append(typeToString(rawType));

        if (typeArguments.length == 0) {
            return stringBuilder.toString();
        }

        stringBuilder.append("<").append(typeToString(typeArguments[0]));
        for (int i = 1; i < typeArguments.length; i++) {
            stringBuilder.append(", ").append(typeToString(typeArguments[i]));
        }
        return stringBuilder.append(">").toString();
    }
}

ParameterizedTypeImpl目前还是很简单, 考虑的不是很周全, 但是用在CoreLibs中, 去解析网络或者其他需要反序列化的数据时也够用了. CoreLibs中使用了ParameterizedTypeImpl的类有PreferencesHelper以及ImageUploadHelper.

Last updated