2018-06-02 8.5 虚拟机对泛型代码的处理

以下部分只是将我在学习笔记中的关于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方法

合成的桥方法中调用了新定义的方法。

你可能感兴趣的:(2018-06-02 8.5 虚拟机对泛型代码的处理)