0. 引言
通过前三节,我们基本上掌握了泛型的基本用法和一些特定的问题解决。但是对泛型的本质原理,还未曾了解。
我们都知道,泛型是作用在编译阶段的,让IDE帮忙检查代码。但是,到了JVM里头和普通类,普通方法,普通接口就没什么两样了。实现这种特性的专业术语叫做“类型擦除”,即在进入 JVM 之前,与泛型相关的信息会被擦除掉。
其实java的这种设计可以理解,本质上泛型引入就是为了避免运行时的强转类型出问题,而且必须兼容老的不带泛型的代码。
1. 代码实际验证
1.1 两个不同类型的集合
List<String> listStr = new ArrayList<>();
List<Integer> listInt = new ArrayList<>();
System.out.println("list is " + listInt.getClass().getName() + ", listInt.getClass() == listStr.getClass() is" + (listInt.getClass() == listStr.getClass()));
打印结果:list is java.util.ArrayList, listInt.getClass() == listStr.getClass() is true
可以看出即使指定了不同类型,最终两者在Jvm中依然是同一个类型对象。
1.2 泛型类的成员变量
class tiger<T> {
private T food;
private void eat(T food) {
this.food = food;
}
}
tiger<String> meat = new tiger<>();
Field[] fs = meat.getClass().getDeclaredFields();
for ( Field f:fs) {
System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}
打印结果:Field name food type:java.lang.Object
可以看到,即使指定了String,擦除类型后还是Object。这也就能够做到兼容老的代码,因为老代码都是默认Object的。
1.3 extends限制类型擦除
class tiger<T extends String> {
private T food;
private void eat(T food) {
this.food = food;
}
}
tiger<String> meat = new tiger<>();
Field[] fs = meat.getClass().getDeclaredFields();
for ( Field f:fs) {
System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}
打印结果:Field name food type:java.lang.String
通过extends 指定了泛型是String的子类,因此类型擦除后,都是String。
2. 理解类型擦除的意义
当我们理解类型擦除,理解了最终到JVM中并没有泛型,就能够理解一些规则,而不必死记硬背。
- 为什么extends能够限制类型的上限,因为类型擦除后,就是它的父类。
- 用反射,可以在List集合中,添加String,因为类型擦除后,都是Object。