Java泛型的内部机制

类型擦除

 事实上,虚拟机是没有泛型类型对象的———所有对象都属于普通类。它所看到的只有原始类型
 无论何时定义一个泛型类型,都自动提供了一个相应的原始类型 ( raw type)。原始类型的名字就是删去类型参数后的泛型类型名。当编译一个泛型类的时候,编译器会擦除那些泛型类型名,并将它转换为第一个限定类型名。这样就得到了原始类型。
 比如说:

class Demo<T extends Comparable & Iterable>
{
    private T field;
    //...
}

 擦除后,T自动被转换为第一个限定类型Comparable,原始类型就是这样的:

Class Demo
{
    private Comparable field;
    //...
}

附注:由以上例子可以知道:为了提高效率,应该将标签(tagging) 接口(即没有方法的接口)放在边界列表的末尾

 而对于没有限定的泛型类:

class Demo<T>
{
    private T field;
    //...
}

 将自动转换为Object

class Demo
{
    private Object field;
    //...
}

也就是说,看上去像是泛型的类,实际上是一个普通的类。就好像泛型引人Java语言之前已经实现的那样。

 而在使用时,在程序中可以包含不同类型的Demo, 例如, Demo<String>Demo<LocalDate>。而擦除类 型后就变成原始的Demo类型了

翻译泛型表达式

 根据以上描述可以得知,在编译过后,虚拟机看到的只有原始类型。所以当使用泛型表达式 时,就需要进行翻译。
 具体而言,当程序调用泛型方法时,如果擦除返回类型, 编译器插入强制类型转换。比如对于:

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst(); 

 在我们编程时,getFirst返回一个类型T的对象;擦除后返回类型成了Object。编译器自动插人Employee 的强制类型转换。 也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
- 对原始方法 Pair.getFirst 的调用
- 将返回的Object 类型强制转换为Employee类型

继承泛型类的的问题

 擦除会带来一个问题,假设有这样一个类:

class Value<T>
{
    private T value;
    public void setValue(T val)
    {
        value = val;
    }
    public T getValue()
    {
        return value;
    }
}

 现在假设有一个新的类继承了这个泛型类:

class Int extends Value<Integer>
{
    @Override
    public void setValue(Integer val)
    {
        super.setValue(val);
    }
    public Integer getValue()
    {
        return super.getValue();
    }
}

多态性与桥方法

说明

 现在,使用一个Value<Integer>的引用调用Int对象的方法,希望可以实现多态性

Int i = new Int();
i.setValue(3);
System.out.println(i.getValue());

Value<Integer> i2 = i;
i2.setValue(4);//是否可以实现多态性呢?
System.out.println(i2.getValue());

 乍一想这似乎不是个问题,但是仔细分析就会发现有问题。以setValue方法为例,由于Value<Integer>在编译后会进行擦除,所以setValue实际上并不是:

public void setValue(Integer val)
{
    value = val;
}

而是而是一个参数类型为Object的方法

public void setValue(Object val)
{
    value = val;
}

 也就是说在擦除后,超类的setValue方法和子类的setValue的参数类型不同,前者为Object而后者为Integer
 但是不应该这样啊!这就意味着我们以为我们在子类Int中覆盖了超类的方法,然而实际上我们只是重新定义了一个同名的重载方法。

class Int extends Value<Integer>
{
    //这是从超类继承的
    public void setValue(Object val){}
    //这是我们新定义的,它并没有实现对超类方法的Override
    public void setValue(Integer val){...}
}

 这就意味着,我们很可能无法实现多态性
 好在,为了解决这个问题,编译器会自动生成一个桥方法

public void setValue(Object val)
{
    setValue((Integer)val);     
}

 通过这个编译器自动生成的桥方法,就可以实现多态性。下面我们来跟踪一下语句的执行过程:

Value<Integer> i2 = i;
i2.setValue(4);
  1. i2是一个Value<Integer>类型,这个类中唯一一个和函数名匹配的方法只有setValue(Object)。虚拟机用引用的对象调用这个方法。
  2. 检测到该对象实际上是一个Int类,于是调用合成的桥方法setValue(Object),多态性实现。

桥方法的检验

 为了证实桥方法确实存在,我们可以利用反射来加以检测。
 首先,出于对照试验的考虑我们来额外定义一组普通的继承体系:

class Value2
{
    private int val = 0;
    public void setValue(int val)
    {
        this.val = val;
    }
    public int getValue()
    {
        return val;
    }
}
class Int2 extends Value2
{
    @Override
    public int getValue()
    {
        return super.getValue();
    }
    @Override
    public void setValue(int val)
    {
        super.setValue(val);
    }
}

 定义一个Int2 i2 = new Int2();调用预先写好的displayDeclaredMethod(ix);。输出如下:

Class name : Int2   
Declared method:   
    int getValue()  
    void setValue(int arg0, )

 然而对于一个Int i的对象,调用displayDeclaredMethod(ix);,输出:

Class name : Int
Declared method: 
    Object getValue()
    Integer getValue()
    void setValue(Object arg0, )
    void setValue(Integer arg0, )

 显然,多出了两个编译器自动生成的桥方法。

方法签名问题

 如果你仔细看一看之前输出的Int的方法,你会发现一个奇怪的地方:

Object getValue()
Integer getValue()

 发现了没有?在一个类中竟然出现了两个方法签名相同的方法!除了返回值,其他的都相同!
 事实上关于区分方法有一个很冷门的知识点:
- 方法签名 确实只有方法名+参数列表 。这毫无疑问!
- 我们绝对不能编写出方法签名一样的多个方法 。如果这样写程序,编译器是不会放过的。这也毫无疑问!
- 事实上,JVM用参数类型和返回类型确定一个方法。因此, 编译器可能产 生两个仅返回类型不同的方法字节码,虚拟机能够正确地处理这一情况。

 简而言之,主要的锅在编译器上。编译器限制了我们的行为,但却给自己“开后门”

引用:
http://blog.csdn.net/pacosonswjtu/article/details/50374131
《Java 核心技术》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值