下面是一个很典型的泛型代码, 来自Core Java Volume Ⅰ。
public class Pair<T> {
private T first;
private T second;
public Pair() { first = null; second = null;}
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public void setFirst(T first) { this.first = first; }
public T getSecond() { return second; }
public void setSecond(T second) { this.second = second; }
JVM没有泛型类型对象,所有的泛型在编译阶段就已经被处理成了普通类和方法。 无论何时定义了一个泛型类型, 都自动提供了一个相应的raw type。raw type指的是出去类型参数后的泛型类型名。擦除(erase)类型变量, 并替换成限定类型(extends)或者替换成Object(没有extends)。即:
- 如果泛型类型的类型变量没有限定类型(如上例中的
Pair<T>
) ,那么我们就用Object作为原始类型; - 如果有限定(
Pair<T extends bondingType>
),我们就bondingType作为原始类型; - 如果有多个限定(
Pair<T extends bondingType1 & bondingType2>
),通常将没有方法的接口放在末尾, 第一个放Class
(如果有)。用第一个边界的类型变量bondingType1类作为原始类型;
当程序调用泛型方法时, 擦除返回类型后编译器会插入强制类型转换。例如:
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
擦除getFirst的返回类型后将返回Object类型, 编译器自动插入Employee的强制类型转换(称为翻译泛型表达式)。不过在构造器方法返回时, 会出现一些问题, 例如:
Pair<String> pair1=new Pair("zerods", 2016); //1
Pair<String> pair2=new Pair<String>("zerods", 2016); //2
以上构造器编译器全部调用的是 Pair(Object first, Object second)。因此代码1中的new Pair(“zerods”, 2016)可以通过, 但后面想要使用String second = pair1.getSecond();
就会报错。但对于代码2却显示没有相应的Pair(String, String)
方法(如下图)。
事实上这是因为new Pair<String>("zerods", 2016)
已经指明了创建对象pair2的类型变量T应该是String的。所以在编译期编译器就知道错误出在第二个参数Integer了。
从以上两行代码可以得出一个结论:创建泛型对象的时候,一定要指出类型变量T的具体类型。让编译器检查出错误,而不是留给JVM运行的时候抛出异常。
类型擦除所带来的两个问题
import java.time.LocalDate;
public class DateInterval extends Pair<LocalDate>{
@Override
public void setSecond(LocalDate second) {
if(second.compareTo(getFirst()) > 0)
super.setSecond(second);
}
@Override
public LocalDate getSecond() {
return (LocalDate) super.getSecond(0.clone();
}
}
public static void main(String[] args) {
DateInterval interval = new DateInterval(...);
Pair<LocalDate> pair = interval; //超类,多态
LocalDate date = LocalDate.of(1910, 6, 22);
pair.setSecond(date);
}
一个DateInterval 对象是一对LocalDate对象, 想要重写方法setSecond来保证第二个值永远不小于第一个值。这个类擦除后变成:
public class DateInteval extends Pair {
public void setSecond(LocalDate second) {...}
}
但是别忘了, DateInterval还从Pair继承了public void setSecond(Object second) {...}
,这显然没有完成重写。当pair调用setSecond时,应该调用DateInterval.setSecond, 这样就形成了冲突。要解决这个问题,编译器在DateInterval类中生成了一个桥方法(bridge method):
public void setSecond(Object second) {setSecond((LocalDate) second);}
如果DateInterval还重写了getSecond方法,在擦除的类型中则存在两个getSecond方法:
Date getSecond()
Object getSecond()//重写定义在父类(Pair)的方法来调用第一个方法,jvm实现,不能编写
Note: 在一个方法覆盖另一个方法时可以指定一个更严格的返回类型,它的机制也是同样使用的桥方法。例如:
public class Employee implements Cloneable {
public Employee clone() throws CloneNotSupportedException{...}
}
//实际上这里Employee有两个clone()
Employee clone()
Object clone() //重写了Object类的clone()方法, 调用上面的方法