【0】README
0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java泛型程序设计 的 翻译泛型表达式+翻译泛型方法 的知识;
【1】翻译泛型表达式
1.1)当程序调用泛型方法时, 如果擦除了泛型返回类型, 编译器插入类型转换;
- 1.1.1)看个荔枝:
Pair<Employee> buddies = ...
Employee buddy = buddies.getFirst();
- 擦除getFirst的返回类型后将返回Object类型。 编译器自动插入 Employee 的强制类型转换。
- 也就是说, 编译器吧这个方法调用翻译为两条虚拟机指令(Commands):
- C1)对原始方法 Pair.getFirst 的调用;
- C2)将返回的Object类型 强制转换为 Employee 类型;
1.2)当存取一个泛型域时也要插入强制类型转换。
- 1.2.1)假设 Pair 类的first 域 和 second 域都是 公有的(这不是种好的编程风格, 但在java语法中,这是合法的)。
表达式: Employee buddy = buddies.first; 也会在结果字节码中插入强制类型转换;
【2】翻译泛型方法
2.1)类型擦除也会出现在泛型方法中。
public static <T extends Comparable> T min(T[] a):是一个完整的方法族;
- 2.1.1)擦除类型后, 只剩下一个方法:
public static Comparable min(Comparable[] a)
- 注意, 类型参数T 已经被擦除了, 只留下了限定类型 Comparable;
2.2)方法擦除带来了两个复杂问题。
- 2.2.1)看个荔枝:
class DateInterval extends Pair<Date>
{
public void setSecond(Date second)
{
if(second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
}
对以上代码的分析(Analysis):
- A1)上述类的类型变量擦除后, 为
class DateInterval extends Pair // after erasure
{
public void setSecond(Date second)
{
if(second.compareTo(getFirst()) >=0)
super.setSecond(second);
}
}
- A2)令人感到奇怪的是, 存在另一个从Pair 继承的setSecond方法, 即
public void setSecond(Object second)
- Attention)此时要注意, DateInterval内部应该是重写了 Pair的 setSecond方法, 结果擦除类型参数后, 就不是重写了, 破坏了类的多态性;
A3)它们显然不是同一种方法, 因为有不同的类型参数, 一个是Object , 而另一个是 Date;(这里是干货)
A4)然而不应该不一样, 考虑下面的语句序列:
DateInterval interval = new DateInterval();
Pair<Date> pair = interval; // OK--assignment to superclass
pair.setSecond(aDate); // "那这条语句调用哪个 setSecond方法呢? 是 setSecond(Object) 还是 setSecond(Date) 呢?"
- A5)这里, 希望对setSecond 的调用 具有多态性, 并调用最合适的那个方法。 由于pair 引用DateInterval 对象,所以应该调用 DateInterval.setSecond;
- A6)出现的问题:在于类型擦除与多态发生了冲突, 确实, 如上面的Attention所说, 变量类型擦除破坏了类的多态性;
- A7)解决方法: 就需要编译器在 DateInterval 类中生成一个桥方法(bridge method):
public void setSecond(Object second) //这里就调用了 重写的 父类 DateInterval(Object)
{setSecond((Date)second)}
2.3)上述引入了桥方法:要想了解他的工作过程, 跟踪下列语句 pair.setSecond(aDate);
- 2.3.1)变量pair 已经说明为类型 Pair , 并且这个类型只有一个简单的方法叫做 setSecond, 即 setSecond(Object);
- 2.3.2)虚拟机用 pair 引用的对象调用这个方法: 这个对象是 DateInterval 类型的, 因而将会调用 DateInterval.setSecond(Object) 方法;
- 2.3.3)这个方法是合成的桥方法: 它调用 DateInterval.setSecond(Date)方法,在正是我们想要的;
2.4)桥方法也可以变得很奇怪, 如 DateInterval 方法覆盖了 getSecond()方法:
class DateInterval extends Pair<ate>
{
public Date getSecond()
{
return (Date) super.getSecond().clone();
}
}
对以上代码的分析(Analysis):
- A1)在擦除的过程中, 有两个getSecond方法:
- A1.1) Date getSecond() // defined in DateInterval
- A1.2) Object getSecond() // overrides the method defined in Pair to call the first method
- A2)不能这样编写代码(因为具有相同参数的两个方法是不合法的, 他们都没有参数)。
A3)但在虚拟机中, 用参数类型和返回类型确定一个方法, 因此, 编译器可能产生两个仅返回 类型不同的 方法字节码, 虚拟机能够正确处理这个情况;
Annotation)A1)桥方法不仅用于泛型类型; 还有, 在一个方法覆盖另一个方法时可以指定一个更严格的返回类型:
public class Employee implements Clonealbe
{
public Employee clone() throws CloneNotSupportedException() {}
}
对以上代码的分析(Analysis):
- A1) Object.clone 和 Employee.clone 方法被说成具有协变的返回类型;(具有协变的返回类型)
- A2)实际上, Employee 类有两个克隆方法(Methods):
- M1) Employee clone();// defined above
- M2) Object clone() ; // synthesized bridge method, overrides Object.clone;
- A3)合成的桥方法调用了新定义的方法;
Conclusion)总之, 需要记住有关java泛型转换的事实:
- C1)虚拟机中没有泛型, 只有普通的类和方法;
- C2)所有的类型参数都是用它们的限定类型替换;
- C3)桥方法被合成来保持多态;
- C4)为保持类型安全性, 必要时插入强制类型转换;