以下部分只是将我在学习笔记中的关于Java核心技术卷一 8.5 的笔记单拆出来,形成一篇文章。
8.5 泛型代码和虚拟机
1.无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased) 类型变量, 并替换为限定类型(无限定的变量用 Object)。
2.java中的泛型只在程序源码中存在,当编译成字节码之后,就会变为原始类型,java实现泛型的方法是类型擦除。
3.当程序调用泛型方法时,如果擦除返回类型, 编译器插入强制类型转换。例如,下面这个语句序列
Pair buddies = . .
Employee buddy = buddies.getFirst();
擦除
getFirst 的返回类型后将返回 Object 类型。编译器自动插人 Employee
的强制类型转换,将返回的Object类型转换为Employee类型。当存取一个泛型域时也要插入强制类型转换。假设 Pair 类的 first
域和 second 域都是公 有的。表达式:
Employee buddy = buddies.first;
就会在结果字节码中插入强制类型转换。
4.类型擦除也会出现在泛型方法中,由此产生一些问题:在一个子类中,如果对于父类的一个函数进性了重载,则在擦除之后就会破坏重载。比如说:
class Datelnterval extends Pair
{
public void setSecond(LocalDate second)
{
if (second.compareTo(getFirstO) >= 0)
super.setSecond(second);
}
}
在被擦除之后,就会变为:
class Datelnterval extends Pair
{
public void setSecond(LocalDate second)
{……}
}
而在父类中,则有一个public
void setSecond(Object second)方法,原本这个方法应该作为模板方法被实例化为public void
setSecond(LocalDate
second)而后被子类中的同名方法所重载,但是此时由于擦除,重载被破坏了,父类的方法与子类中的这个方法变为了两个方法,public void
setSecond(Object second)和public void setSecond(LocalDate
second)共同出现在子类中。
因此,编译器的解决方法是在作为子类的Datelnterval类中,生成一个桥方法,即为:
public
void setSecond(Object second) { setSecond((Date) second);
},这个方法使用了与父类方法在擦除之后相同的函数名,返回值,与参数列表,从而完全将父类方法覆盖,而这个桥方法的作用则是,将变量强制类型转换之后,传入子类方法,而在实际使用时,如果一个Pair的引用引用了一个Datelnterval类的变量,在接收参数时,即使调用桥函数,也会和以前一样运行。
5.假设Datelnterval类也重载了getSecond()方法,比如说:
class Datelnterval extends Pair
{
public LocalDate getSecond()
{
return (Date) super.getSecond().clone();
}
……
}
那么,在擦除之后,这个类中就会有两个函数:
public LocalDate getSecond()
public Object getSecond()/*这是一个桥方法,它将Pair类中的方法给覆盖掉了*/
由于虚拟机中,是使用参数类型和返回类型唯一确定一个方法,因此虽然在源码中无法写出如此形式,但是在最后生成的字节码中,却会出现以上这种情况。
6.这也导致了其他问题,比如说:
public class TestTheBug
{
public static void method (Pair pairex) { System.out.println("Pair string pairex"); }
public static void method (Pair pairex) { System.out.println("Pair int pairex"); }
}
这段代码表面上没有问题,但事实上是无法进行编译的,因为在进性擦除之后,这两个函数的函数头部已经完全一致了,由此造成错误。但是,如果你进行这样的修改:
public class TestTheBug
{
public static string method (Pair pairex)
{
System.out.println("Pair string pairex");
return " "
}
public static int method (Pair pairex)
{
System.out.println("Pair int pairex");
return 1;
}
}
这两个方法就可以照常运行,因为通过不同的参数列表,通过了编译器,而后又通过不同的返回值,使得虚拟机也可以区分。
7.桥方法不仅用于泛型类型。 在一个方法覆盖另一个方法时可以指定一个更严格的返回类型。例如:
public class Employee implements Cloneable
{
public Employee clone() throws CloneNotSupportedException { ...}
}
Object.clone 和 Employee.clone 方法被说成具有协变的返回类型(covariant returntypes)。 实际上,Employee 类有两个克隆方法:
Employee clone() // defined above
Object clone() // 合成的桥方法,覆盖了原本的Object.clone方法
合成的桥方法中调用了新定义的方法。