由于整个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在反序列化带有泛型的实体时, 建议我们使用如下方法:
Copy Type type = new TypeToken<List<Data>>() {}.getType();
List<Data> list = gson.fromJson(json, type);
通过TypeToken这个类, Gson就能获取TypeToken中声明的泛型的实际类型, 在上述代码中就是List<Data>
. TypeToken内部究竟是如何实现的呢? 主体上就是通过下列代码:
Copy Type mySuperClass = getClass().getGenericSuperclass();
Type type = ((ParameterizedType) mySuperClass).getActualTypeArguments()[0];
通过getGenericSuperclass()来获取ParameterizedType, 并通过ParameterizedType的getActualTypeArguments()来获取泛型的类型. 但是最关键的还是getClass()调用的时机. getClass()方法是在TypeToken类内部调用的, 也就意味着getClass()返回的就是TypeToken的class. 接下来我们做个小实验:
Copy Test<Data> data = new Test<>();
Type mySuperClass = data.getClass().getGenericSuperclass();
Type type = ((ParameterizedType) mySuperClass).getActualTypeArguments()[0];
System.out.println(type);
输出结果:
Copy 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类型, 因此我们无法通过此种方式获取泛型的实际类型. 但是换一种写法却会有不一样的结果:
Copy Test<Data> data = new Test<Data>() {};
Type mySuperClass = data.getClass().getGenericSuperclass();
Type type = ((ParameterizedType)mySuperClass).getActualTypeArguments()[0];
System.out.println(type);
输出结果:
Copy 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接口入手.
首先我们来看看上面代码中的实体类的定义:
Copy 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以用来获取泛型实际类型的工具类. 使用方法:
Copy ParameterizedType type = ParameterizedTypeImpl.get(Test.class, Data.class);
Test<Data> test = gson.fromJson(json, type);
如果泛型的层级很多, 可以以此类推:
Copy ParameterizedType type = ParameterizedTypeImpl.get(Test.class, List.class, Data.class);
Test<List<Data>> tests = gson.fromJson(json, type);
ParameterizedTypeImpl源码:
Copy 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 .