• Java泛型解析(03):虚拟机运行泛型代码


    Java泛型解析(03):虚拟机运行泛型代码

         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 staticComoparable 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) {...}
         所以DateInterval从Period中继承了 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以后添加了一个协变类型,使得这里是被同意的,看看DateInterval中getBegin方法就知道了:
    [code09]
         @Override
         public Date getBegin(){ return super.getBegin(); }
         这里用了@Override,说明是覆盖了父类的Object getBegin()方法,而返回值能够指定为父类中的返回值类型的子类,这就是协变类型,这是Java5以后才干够同意的,同意子类覆盖了方法后指定一个更严格的类型(子类型)。
    总结:
         1.记住一点,虚拟机中没有泛型,仅仅有普通的类。
         2.全部泛型的类型參数都用它们限定的类型取代,没有限定则用Object。
         3.为了保持类型安全性,虚拟机在有必要时插入强制类型转换。
         4.桥方法的合成用来保持多态性。
         5.协变类型同意子类覆盖方法后返回一个更严格的类型。


    =====【感谢亲阅读寻常心的文章,亲若认为此文有帮助,顶一顶亲的支持将给我前进的动力】=====


  • 相关阅读:
    httpclient 使用问题记录:org.apache.http.HttpException: Unsupported Content-Coding:GLZip
    Gitserver端密码变更,但是本地gitconfig配置未变更账号和密码问题解决
    线程池ThreadPoolExecutor学习
    Java 网络编程
    org.apache.ibatis.binding.BindingException: Invalid bound statement Mybatis绑定错误问题解决
    Java string类
    maven3.6.2 版本 在idea 2019.2.2下遇到的问题解决记录
    python
    django-URL与视图配置
    python 的datetime模块使用
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4283020.html
Copyright © 2020-2023  润新知