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.协变类型允许子类覆盖方法后返回一个更严格的类型。

Java泛型解析(01):认识泛型

Java泛型解析(02):通配符限定

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

时间: 2024-12-10 20:14:51

Java泛型解析(03):虚拟机执行泛型代码的相关文章

Java泛型解析(01):认识泛型

What Java从1.0版本到现在的8,中间Java5中发生了一个很重要的变化,那就是泛型机制的引入.Java5引入了泛型,主要还是为了满足在1999年指定的最早Java规范之一.经过了5年左右的时间,专家组定义了一套泛型规范,实现后通过测试投入到使用.所以说泛型是Java5以后才有的,欲知详情,继续往下看. Why      换个角度想,Java5引入泛型,必定是它能带来好处,否则牛气的Java专家工程师就要遭到吐槽了.我们来吐槽一下没有泛型的程序是怎么写的. [code01] ArrayL

Java泛型解析(04):约束和局限性

Java泛型解析(04):约束和局限性 前两节,认识和学习了泛型的限定以及通配符,初学者可能需要一些时间去体会到泛型程序设计的好处和力量,特别是想成为库程序员的同学就需要下去体会通配符的运用了,应用程序员则需要掌握怎么使用泛型,这里针对泛型的使用中的约束和局限性做一个介绍性的讲解. 不能用基本类型实例化类型参数 这个很好理解,实例化泛型类时泛型参数不能使用基本类型,如List<int>这是不合法的,要存储基本类型则可以利用基本类型的包装类如List<Integer> .List&l

Java泛型解析(02):通配符限定

Java泛型解析(02):通配符限定 考虑一个这样的场景,计算数组中的最大元素. [code01] public class ArrayUtil { public static <T> T max(T[] array) { if (array == null || 0 == array.length) { return null ;} T max = array[0]; for (int i = 1; i < array.length; i++) { if (max.compareTo(

java泛型边界深入探讨,示例代码

package org.rui.generics.bounds; import java.awt.Color; /** * 边界 * 下面展示了边界基本要素. * @author lenovo * */ interface HasColor{java.awt.Color getColor();} class Colored<T extends HasColor> { T item; Colored(T item){this.item=item;} T getItem(){return item

使用GSON和泛型解析约定格式的JSON串(转)

时间紧张,先记一笔,后续优化与完善. 解决的问题: 使用GSON和泛型解析约定格式的JSON串. 背景介绍: 1.使用GSON来进行JSON串与java代码的互相转换. 2.JSON的格式如下三种: 写道 #第一种: {"success":true,"data":{"averageStarLevel":4.7,"remarkCount":10}} #第二种: {"success":true,"da

重读《深入理解Java虚拟机》五、虚拟机如何执行字节码?虚拟机执行引擎的工作机制

Class文件二进制字符流通过类加载器和虚拟机加载到内存(方法区)完成在内存上的布局和初始化后,虚拟机字节码执行引擎就可以执行相关代码实现程序所定义的功能.虚拟机执行引擎执行的对象是方法(均特指非本地方法),方法是 着一个程序所定义的一个功能的载体,实现预定的业务功能或者特定的功能等. Java虚拟机内存内针对方法的执行专门划分了一个区域即虚拟机栈.虚拟机栈内通过栈帧结构来存储调用方法和执行方法需要的局部变量,操作数栈.方法返回值等,通过栈帧的出入栈来表示方法的执行顺序. 1.栈帧结构:虚拟机内

Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 概要 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayList.先对ArrayLis

java泛型(二)、泛型的内部原理:类型擦除以及类型擦除带来的问题

java泛型(二).泛型的内部原理:类型擦除以及类型擦除带来的问题 参考:java核心技术 一.Java泛型的实现方法:类型擦除 前面已经说了,Java的泛型是伪泛型.为什么说Java的泛型是伪泛型呢?因为,在编译期间,所有的泛型信息都会被擦除掉.正确理解泛型概念的首要前提是理解类型擦出(type erasure). Java中的泛型基本上都是在编译器这个层次来实现的.在生成的Java字节码中是不包含泛型中的类型信息的.使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉.这个过程就称为类型

java泛型(一)、泛型的基本介绍和使用

现在开始深入学习java的泛型了,以前一直只是在集合中简单的使用泛型,根本就不明白泛型的原理和作用.泛型在java中,是一个十分重要的特性,所以要好好的研究下. 泛 型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型.这种参数类型可以用在类.接口和方法的创建中,分别称为泛 型类.泛型接口和泛型方法. 泛型思想早在C++语言的模板(Templates)中就开始生根发芽