Java泛型擦除

虚拟机没有泛型类型对象——所有对象都属于普通类。

  1. 类型擦除
    无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,并替换为其限定类型(或者,对于无限定的变量则替换为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; }
}
  1. 转换泛型表达式
    编写一个泛型方法调用时,如果擦除了返回类型,编译器会插入强制类型转换。例如,对于下面这个语句序列
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

getFirst擦除类型后的返回类型时Object。编译器自动插入到Employee的强制类型转换。也就是说,编译器把这个方法调用转换为两条虚拟机指令:

  • 对原始方法Pair.getFirst的调用
  • 将返回的Object类型强制转换为Employee类型。
  1. 转换泛型方法
    类型擦除也会出现在泛型方法中,看一看下面的示例:
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泛型的转换,需要记住以下几个事实:

  • 虚拟机中没有泛型,只有普通的类和方法。
  • 所有的类型参数都会替换为它们的限定类型。
  • 会合成桥方法来保持多态。
  • 为保持类型安全性,必要时会插入强制类型转换。