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方法

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 使用教材:java核心技术 卷一 第十版 8.1 1.在 Java 中增加范型类之前, 泛型程序设计是用继承实现的...
    静者达观阅读 309评论 1 1
  • 引言:泛型一直是困扰自己的一个难题,但是泛型有时一个面试时老生常谈的问题;今天作者就通过查阅相关资料简单谈谈自己对...
    cp_insist阅读 1,867评论 0 4
  • 5继承 5.1 类、超类和子类 重用部分代码,并保留所有域。“is-a”关系,用extends表示。 已存在的类被...
    我快要上天啦阅读 842评论 1 3
  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 2,040评论 0 3
  • 今天在超市发现了一种新的菌菇,名字叫猪肚菇,我当机立断买下来,晚上回家做碗菌菇汤面,味道鲜美,即使一碗面也要精工细...
    阿紫lisa阅读 357评论 0 0