擦拭法
782字约3分钟
2024-08-08
泛型的局限
Java
语言的泛型实现方式是擦拭法(Type Erasure
),所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的
编写 Animal<T>
泛型类时候,编译器看到的代码如下
public class Animal<T> {
private T name;
private T type;
public Animal(T name, T type) {
this.name = name;
this.type = type;
}
public T getName() {
return name;
}
public T getType() {
return type;
}
}
虚拟机根本不知道泛型,虚拟机执行的代码如下
public class Animal {
private Object name;
private Object type;
public Animal(Object name, Object type) {
this.name = name;
this.type = type;
}
public Object getName() {
return name;
}
public Object getType() {
return type;
}
}
我们使用泛型的时候,我们编写和编译器看到的代码如下
Animal<String> animal = new Animal<>("小猫", "猫科");
String name = animal.getName();
String type = animal.getType();
虚拟机执行的代码并没有泛型
Animal animal = new Animal("小猫", "猫科");
String name = (String) animal.getName();
String type = (String) animal.getType();
局限一:<T>
不能是基本类型,例如int
,因为实际类型是Object
,Object
类型无法持有基本类型
Animal<int> p = new Animal<>(1, 2); // compile error!
局限二:无法取得带泛型的Class
public static void main(String[] args) {
Animal<String> a1 = new Animal<>("小猫", "猫科");
Animal<Integer> a2 = new Animal<>(1, 2);
Class c1 = a1.getClass();
Class c2 = a2.getClass();
System.out.println(c1 == c2); // ture
System.out.println(c1 == Animal.class); // ture
}
所有泛型实例,无论T的类型是什么,getClass()
返回同一个 Class
实例,因为编译后它们全部都是 Pair<Object>
局限三:无法判断带泛型的类型
public static void main(String[] args) {
Animal<String> a1 = new Animal<>("小猫", "猫科");
// 编译报错
if (a1 instanceof Animal<String>) {
}
}
原因和局限二一样,并不存在 Pair<String>.class
,而是只有唯一的 Pair.class
局限四:不能实例化T
类型
public class Animal<T> {
private T name;
private T type;
// 编译报错
public Animal() {
name = new T();
type = new T();
}
}
编译报错原因
name = new T();
type = new T();
// 泛型擦拭后实际上变成了
name = new Object();
type = new Object();
这样一来,创建 new Pair<String>()
和创建 new Pair<Integer>()
就全部成了 Object
,显然编译器要阻止这种类型不对的代码
要实例化 T
类型,我们必须借助额外的 Class<T>
参数
public class Animal<T> {
private T name;
private T type;
public Animal(Class<T> clazz) throws IllegalAccessException, InstantiationException {
name = clazz.newInstance();
type = clazz.newInstance();
}
}
不恰当的覆写方法
有些时候,一个看似正确定义的方法会无法通过编译。例如
public class Animal<T> {
public boolean equals(T t) {
return this == t;
}
}
定义的 equals(T t)
方法实际上会被擦拭成 equals(Object t)
,而这个方法是继承自 Object
的,编译器会阻止一个实际上会变成覆写的泛型方法定义
泛型继承
一个类可以继承自一个泛型类,例如:父类的类型是 Animal<String>
,子类的类型是 Cat
,可以这么继承
public class Cat extends Animal<String> {
}
在父类是泛型类型的情况下,编译器就必须把类型 T
(对 Cat
来说,也就是 String
类型)保存到子类的 class
文件中,不然编译器就不知道 Cat
只能存取 String
这种类型