Java泛型
在Java中,泛型是通过擦除法来实现的,这意味着无法在运行时直接获取到对象的泛型信息,因为所有的泛型参数全部会被替换为Object,例如:
1
| List<Person> personList = new ArrayList();
|
上面这行代码在运行时与以下代码无异:
1
| List personList = new ArrayList();
|
通过子类来保存父类泛型参数中的类型
在前面的例子中,我们想尝试通过类的实例来获取泛型信息,这在使用擦除法的Java中是行不通的,但是Java有另一个特性,那就是如果一个类继承另一个泛型类时,指明了其泛型参数,那么这个泛型参数就会被编译到这个类的字节码中,可以供运行时获取。光这样说有点抽象,举个例子:
1 2 3 4 5 6 7
| class Parent<T> { }
class Child extends Parent<Person> {}
|
上面的例子中,Child类在继承Parent时,指明了其泛型参数是Person类型,在编译时,这个Person类型的信息就会被编译到Child类的字节码中。
那我们如何获取编译进字节码的类型信息呢?答案是使用Class对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Child extends Parent<Person> { private final Type type; protected Parent() { Type superclassType = getClass().getGenericSuperclass(); if (superclassType instanceof ParameterizedType) { type = ((ParameterizedType) superclassType).getActualTypeArguments()[0]; } else { throw new RuntimeException("获取泛型参数异常"); } }
Type getType() { return this.type; } }
|
Type是一个接口,是比Class更高层次的抽象,Class是Type的实现类之一
除了Class之外,Type的实现类还有上面代码中出现的ParameterizedType,其表示的含义是:含泛型参数的类型,例如Parent<Person>、List<String>等。Type接口的其他实现类:
| 实现类型 |
示例 |
含义 |
| GenericArrayType |
T[] |
泛型数组类型 |
| TypeVariable<?> |
<T> 中的 T |
泛型变量 |
| WildcardType |
? extends Number |
通配符类型 |
有了Type接口及其实现类,我们可以完成很多有意思的事情。
ParameterizedType 的一种实际应用
在刚才的Child、Parent例子中,我们发现可以利用子类来充当父类泛型类型的容器以在运行时保留泛型信息,但是每次都需要定义一个子类实在是太麻烦了,我们可以使用Java的匿名类特性来简化这一过程:
1 2 3 4 5 6
| class Parent<T> {}
Parent<String> anonymousClassObj = new Parent<String>(){};
|
注意,既然这个类是匿名的,我们自然无法获取到这个匿名类本身,只能创建这个匿名类的对象
那么如何从这个对象中获取泛型信息呢?
我们可以把前面Child类中的内容移动到Parent类中去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Parent<T> { private final Type type; protected Parent() { Type superclassType = getClass().getGenericSuperclass(); if (superclassType instanceof ParameterizedType) { type = ((ParameterizedType) superclassType).getActualTypeArguments()[0]; } else { throw new RuntimeException("获取泛型参数异常"); } }
Type getType() { return this.type; } }
|
之所以可以将其移动到Parent类中去,得多亏了Java的动态绑定机制,里面的getClass方法实际是调用的子类型上的,在本例中,是我们创建的匿名类。
接下来试试效果:
1 2 3 4 5 6
| public class Main { public static void main(String[] args) { Parent<String> anonymousClassObj = new Parent<String>() {}; System.out.println(anonymousClassObj.getType()); } }
|
输出结果:
成功在运行时获取到了泛型参数的实际类型!
实际上,这种方式就是Gson用来实现运行时获取类型的方法,我们将上面Parent重命名为TypeToken,写出来就是下面的样子:
1 2 3 4 5 6
| public class Main { public static void main(String[] args) { TypeToken<String> anonymousClassObj = new TypeToken<String>() {}; System.out.println(anonymousClassObj.getType()); } }
|
这正是使用Gson库时常写的模板代码之一。
神不知鬼不觉就理解了Gson TypeToken的实现原理