泛型和虚拟机

下面是一个很典型的泛型代码, 来自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()方法, 调用上面的方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值