Java泛型擦除
虚拟机没有泛型类型对象——所有对象都属于普通类。
- 类型擦除
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,并替换为其限定类型(或者,对于无限定的变量则替换为Object)。
例如,Pair的原始类型如下所示:
public class val
{
private Object value;
public Pair(Object value)
{
this.value = value;
}
public Object getValue(){ return value; }
public void SetValue(Object value){ this.value = value; }
因为T是一个无限定的变量,所以直接用Object替换。
在程序中可以包含不同类型的val,例如,val或val。不过擦除后,它们都会变成原始的val类型。
原始类型用第一个限定来替换类型变量,或者,如果没有给定限定,就替换为Object。
假定我们声明了一个稍有不同的类型:
public class val<T extends Comparable & Serializable> implements Serializable
{
pivate T value;
public val(T value){ this.value = value; }
}
原始类型val如下所示:
public class val implements Serializable
{
private Comparable value;
public val(Comparable value){ this.value = value; }
}
- 转换泛型表达式
编写一个泛型方法调用时,如果擦除了返回类型,编译器会插入强制类型转换。例如,对于下面这个语句序列
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
getFirst擦除类型后的返回类型时Object。编译器自动插入到Employee的强制类型转换。也就是说,编译器把这个方法调用转换为两条虚拟机指令:
- 对原始方法Pair.getFirst的调用
- 将返回的Object类型强制转换为Employee类型。
- 转换泛型方法
类型擦除也会出现在泛型方法中,看一看下面的示例:
class DateInterval extends Pair<LocalDate>
{
public void setSecond(LocalDate second)
{
if (second.compareTo(getFirst()) >= 0 )
super.setSecond(second);
}
}
日期区间是一对LocalDate对象,而且我们需要覆盖这个方法来确保第二个值永远不小于第一个值。这个类擦除后变成
class DateInterval extend Pair
{
public void setSecond(LocalDate second){...}
...
}
令人感到奇怪的是,还有另一个从Pair继承的setSecond方法,即
public void setSecond(Object second)
不过,不应该不一样。考虑下面的语句序列:
DateInterval interval = new DateInterval(...);
Pair<LocalDate> pair = interval;
pair.setSecond(aDate);
这显然是一个不同的方法,因为它有一个不同类型的参数——Object,而不是LocalDate。不过这里,我们希望setSecond调用具有多态性,会调用最合适的那个方法。由于pair引用一个DateInterval对象,所以应该调用DateInterval.setSecond。问题在于类型擦除与多态发生了冲突。为了解决这个问题,编译器在DateInterval类中生成了一个桥方法:
public void setSecond(Object second){ setSecond((LocalDate) second); }
总之,对于Java泛型的转换,需要记住以下几个事实:
- 虚拟机中没有泛型,只有普通的类和方法。
- 所有的类型参数都会替换为它们的限定类型。
- 会合成桥方法来保持多态。
- 为保持类型安全性,必要时会插入强制类型转换。