【Java基础面试题034】Java泛型擦除是什么?
回答重点
泛型擦除指的是Java编译器在编译时将所有泛型信息删除的过程,以确保与Java1.4及之前的版本保持兼容
泛型参数在运行时会被替换为其上界(通常是Object),这样一来在运行时无法获取泛型的实际类型。
作用:泛型擦除确保了Java代码的向下兼容性,即可以与旧版本的Java代码兼容,为了让使用泛型的代码在不同版本的Java运行时环境中都可以正常工作。又由于泛型类型信息在编译时期被擦除了,因此运行时无法获取泛型信息,这样就不能创建泛型类型的数组或对泛型类型使用instanceof检查
示例:
public <T> void printList(List<T> list) {
for (T element : list) {
System.out.println(element);
}
}
编译后的代码类似于:
public void printList(List list) {
for (Object element : list) {
System.out.println(element);
}
}
类型T会被擦除成Object
扩展知识
为什么Java泛型的实现是类型擦除?
回答重点提到了主要原因是为了向下兼容,即兼容Java5之前编译的class文件。
例如Java1.2上正在跑的代码,可以在Java1.5上的JRE上运行
也是因为需要向下兼容,才使得Java实现的是伪泛型
我从现有的实现倒推伪泛型的设计可能思路(个人瞎掰的,做个参考即可)
是这样的:
- 这Java5以前的版本在线上已经有很多应用在跑了,如果我的Java5不能兼容,没人用啊
- 泛型毕竟是加一个约束,以前的代码没有这个约束啊,该如何兼容?
- 有了,要不我在编译器上动手动脚,在编译的时候识别和约束泛型,然后编译过了就把泛型的信息擦除了。这样运行的时候就没有约束了,跟之前的版本就没什么区别了。
参考网上的解释:
这说明,写Java的也是程序员,也是要发版有上线需求的
为什么运行期通过反射可以获得类型?
public class GenericTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);
}
}
看字节码
从反编译看生成的字节码文件能看到,new的List没有保存泛型,所以是被擦除了
下面有一步checkcast强转为String,可是Java代码中不需要写强转呢?
因为编译器隐形的帮我们插入了强转的代码!所以不需要我们写
再看标题:既然擦除了类型,为什么运行期还能通过反射获取类型?
答案就藏在class文件中
看下面代码:
获取泛型类型
public class GenericTest {
public List<String> list;
public static void main(String[] args) throws Exception{
Field field = GenericTest.class.getField("list");
Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
System.out.println(type);
}
}
javap -v,可以看到字节码里记录了泛型类型信息,所以编译器虽然擦除了泛型类型,也记录了泛型信息,自然能通过反射获取
由于静态记录在了字节码中,所以局部变量这种存在栈中的泛型类型,通过反射就无法获取
只有三种情况可以通过反射获取泛型类型:
- 成员变量的泛型
- 方法入参的泛型
- 方法返回值的泛型
- 类的泛型