美文网首页java学习JAVA 基础知识
JAVA泛型•虚拟机执行泛型代码

JAVA泛型•虚拟机执行泛型代码

作者: 袁慎建 | 来源:发表于2016-09-27 20:54 被阅读129次

为了更好地阅读体验,欢迎访问博客原文

虚拟机中类型擦除

Java虚拟机是不存在泛型类型对象的,所有的对象都属于普通类,甚至在泛型实现的早起版本中,可以将使用泛型的程序编译为在1.0虚拟机上能够运行的class文件,这个向后兼容性后期被抛弃了,所以后来如果用Sun公司的编译器编译的泛型代码,是不能运行在Java5.0之前的虚拟机的,这样就导致了一些实际生产的问题,如一些遗留代码如何跟新的系统进行衔接,要弄明白这个问题,需要先了解一下虚拟机是怎么执行泛型代码的。

虚拟机的一种机制:擦除类型参数,并将其替换成特定类型,没有指定特定类型用Object代替,如前文中的Couple<T>类,虚拟机擦除后:

[code01]

public class Couple {
    private Object wife ;
    private Object husband ;

    public Couple(Object  wife, Object  husband) {
        this.wife = wife;
        this.husband = husband;
    }
    public void setWife(Object  wife) {this. wife = wife;}
    public void setHusband(Object  husband) {this. husband = husband;}
          
    public Object  getWife() {return wife;}
    public Object  getHusband() {return husband;}
}

类型参数T是一个任意类型的,所以擦除后用Object代替了。不管是Couple<Employee>或者Couple<String>擦除后都成为了原始类Couple类,这就好比回到了泛型引入Java之前的普通类。所以这里重点围绕着擦除类型参数这个机制展开讲解。

如有对类型参数有类型限定会怎么替换呢?擦除类型参数机制告诉我们,使用限定的类型代替,如果有多个,使用第一个代替,看一段代码:

[code02]

public class Period<T extends Comparable<T> & Serializable> {
    private T begin;
    private T end;

    public Period(T one, T two) {
        if (one.compareTo(two) > 0) {
            begin = two;end = one;
        } else {
            begin = one;end = two;
        }
    }
}

code02擦除后,Period的原始类型如下:

[code03]

public class Period {
    private Comparable begin;
    private Comparable end;

    public Period(Comparable one, Comparable two) {
        if (one.compareTo(two) > 0) {
            begin = two; end = one;
        } else {
            begin = one; end = two;
        }
    }
}

思考一下,如果将Period<T extends Comparable<T> & Serializable>写成Period<T extends Serializable & Comparable<T>>会是怎么样呢?


标签接口

同理,擦除后原始类型用第一个Serializable代替,这样进行compareTo方法调用的时候,编译器会进行必要的强制类型转换,所以为了提高效率,将标签接口(没有任何方法的接口,也叫tagging接口)放在后面。

先来看看虚拟机执行表达式的时候发生了什么,如:

[code04]

Couple<Employee> couple = ...;
Employee wife = couple.getWife();

擦除后,getWife()返回的是Object类型,然后虚拟机会插入强制类型转换,将Object转换为Employee,所以虚拟机实际上执行了两天指令:

  1. 调用Couple.getWife()方法。
  2. Object转换成Employee类型。

再来看看虚拟机执行泛型方法的时候发生了什么,泛型方法如:

[code05]

public static <T extends Comparable<T>> max(T[] arrays) {... }

擦除后成了:

public static Comoparable max(Comparable[] arrays) {... }

但是泛型方法的擦除会带来两个复杂的问题,且看第一个实例,一个实例:

[code06]

public class Period <T extends Comparable<T> & Serializable> {
    private T begin;
    private T end;

    public Period(T one, T two) {
        if (one.compareTo(two) > 0) {
            begin = two;end = one;
        } else {
            begin = one;end = two;
        }
    }
    public void setBegin(T begin) {this. begin = begin;}
    public void setEnd(T end) {this. end = end;}
    public T getBegin() {return begin;}
    public T getEnd() {return end;}
}
public class DateInterval extends Period<Date> {

    public DateInterval(Date one, Date two) {
        super(one, two);
    }
    public void setBegin(Date begin) {
        super.setBegin(begin);
    }
}

DateInterval类型擦除后,Period中的方法变成:

public void setBegin(Object begin) {...}

DateInterval中的方法还是:

public void setBegin(Date begin) {...}

所以DateIntervalPeriod中继承了public void setBegin(Object begin) {...}而自身又存在public void setBegin(Date begin) {...}方法,用户使用时问题发生了。


桥方法

[code07]

Period<Date> period  = new DateInterval(...);
period.setBegin(new Date());

这里因为period引用指向了DateInterval实例,根据多态性,setBegin应该调用DateInterval对象的setBegin方法,可是这个擦除让Period中的 public void setBegin(Object begin) {...}被调用,导致了擦除与多态发生了冲突,怎么办呢?虚拟机此时会在DateInterval类中生成一个桥方法(bridge method),调用过程发生了细微的变化:

[code08]

public void setBegin(Object begin) {
    setBegin((Date)begin);
}

有了这个合成的桥方法以后,code07中对setBegin的调用步骤如下:

1. 调用DateInterval.setBegin(Object)方法。
2. DateInterval.setBegin(Object)方法调用DateInterval.setBegin(Date)方法。

发现了吗,当我们在DateInterval中增加了getBegin方法之后会是什么样子的呢?是不是Peroid中有一个Object getBegin()的方法,而DateInterval中有一个Date getBegin()方法呢,这两个方法在Java中是不能同时存在的?可是Java5以后增加了一个协变类型,使得这里是被允许的,看看DateIntervalgetBegin方法就知道了:

[code09]

@Override
public Date getBegin(){ return super.getBegin(); }

这里用了@Override,说明是覆盖了父类的Object getBegin()方法,而返回值可以指定为父类中的返回值类型的子类,这就是协变类型,这是Java5以后才可以允许的,允许子类覆盖了方法后指定一个更严格的类型(子类型)。


总结

1. 记住一点,虚拟机中没有泛型,只有普通的类。
2. 所有泛型的类型参数都用它们限定的类型代替,没有限定则用Object。
3. 为了保持类型安全性,虚拟机在有必要时插入强制类型转换。
4. 桥方法的合成用来保持多态性。
5. 协变类型允许子类覆盖方法后返回一个更严格的类型。

相关文章

  • 泛型依赖注入

    泛型依赖注入 1.定义泛型 Java代码: 2.实现基类,声明泛型 Java代码: xml代码:

  • JAVA泛型•虚拟机执行泛型代码

    为了更好地阅读体验,欢迎访问博客原文 虚拟机中类型擦除 Java虚拟机是不存在泛型类型对象的,所有的对象都属于普通...

  • 唬人的Java泛型并不难

    泛型 上面的代码有什么区别? 泛型初探 1、为何引入泛型? Java 泛型也是一种语法糖,使用泛型可以在代码编译阶...

  • Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型

    Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型 在代码进入和离开的边界处,会处理泛型 Java泛型作...

  • 泛型

    JAVA 如何实现泛型(参数化的类型) 为什么需要泛型 1、多种数据类型执行相同的代码image.png 2、泛型...

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

  • Kotlin学习笔记-其他

    一.泛型 在使用java的泛型时候,下面的代码无法通过编译的。因为为了泛型的类型安全,Java 中的泛型类型是 不...

  • Java泛型:类型擦除

    前情回顾 Java泛型:泛型类、泛型接口和泛型方法 类型擦除 代码片段一 显然在平时使用中,ArrayList (...

  • Java-08 泛型 、ArrayList

    泛型 (Generics) 从java 5开始,增加了泛型技术 什么是泛型? 将类型变为参数,提高代码的复用率 泛...

  • Java基础之<泛型>

    泛型类和泛型方法 泛型是Java语言中实现程序多态的一种重要方法,泛型多用于底层代码中,以此来保证代码的通用型。今...

网友评论

    本文标题:JAVA泛型•虚拟机执行泛型代码

    本文链接:https://www.haomeiwen.com/subject/cnqhyttx.html