泛型代码和虚拟机
来源:互联网 发布:qq音乐网站源码 编辑:程序博客网 时间:2024/06/05 05:44
泛型代码和虚拟机
虚拟机没有泛型类型对象—所有对象都属于普通类。
接下来的部分,你将看到编译器是如何“擦除”类型参数的?这个过程对Java程序员有什么影响?
1.类型擦除
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删除类型参数后的泛型类型名。类型变量被擦除(erased),并被其限定类型替代(无限定的变量用Object)。
例如,泛型类型Pair<T>
:
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 T getSecond(){ return second; } public void setFirst(T newValue){ first = newValue; } public void setSecond(T newValue){ second = newValue; }}
其原始类型:
public class Pair { private Object first; private Object second; public Pair(){ first = null; second = null; } public Pair(Object first, Object second){ this.first = first; this.second = second; } public Object getFirst(){ return first; } public Object getSecond(){ return second; } public void setFirst(Object newValue){ first = newValue; } public void setSecond(Object newValue){ second = newValue; }}
因为T是一个未限定的类型变量,它只是被Object替换了。
结果是一个普通类,就像泛型引入Java语言之前你已经实现的一样。
你的程序可能包含不同的Pair类型,如Pair<String>
或Pair<LocalDate>
,但是擦除使它们都变成了原始的Pair类型。
原始类型用第一个限定类型替换类型变量,没有给定限定类型用Object替换类型变量。例如,在类Pair<T>
中的类型变量没有显式的限定,因此原始类型用Object替换T。假设我们声明了一个稍微不同的类型:
public class Interval<T extends Comparable & Serializable> implements Serializable { private T lower; private T upper; public Interval(T first, T second){ if (first.compareTo(second) <= 0){ lower = first; upper = second; } else { lower = second; upper = first; } }}
泛型类Interval的原始类型:
public class Interval implements Serializable { private Comparable lower; private Comparable upper; public Interval(Comparable first, Comparable second){ if (first.compareTo(second) <= 0){ lower = first; upper = second; } else { lower = second; upper = first; } }}
注意:
你可能想要知道切换限定:class Interval<T extends Serializable & Comparable>
会发生什么。如果这样做,原始类型用Serializable替换T,而编译器在必要时要向Comparable插入强制类型转换。为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在限定列表的末尾。
2.翻译泛型表达式
当程序调用一个泛型方法时,当返回类型被擦除后,编译器会插入强制类型转换。例如,下面的语句序列:
Pair<Employee> buddies = ...;Employee buddy = buddies.getFirst();
擦除getFirst的返回类型后,有返回类型Object。编译器自动插入Employee的强制类型转换。即,编译器把这个方法调用翻译为两条虚拟机指令:
- 对原始方法Pair.getFirst的调用
- 将返回的Object类型强制转换为Employee类型。
当访问一个泛型域时也要插入强制类型转换。假设Pair类的first域和second域都是公有的(这也许不是一种好的编程风格,但在Java中是合法的)。表达式:
Employee buddy = buddies.first;
也会在结果字节码中插入强制类型转换。
3.翻译泛型方法
类型擦除也会出现在泛型方法中。程序员通常认为一个泛型方法:
public static <T extends Comparable> T min(T[] a)
是一个完整的方法族,但擦除类型之后,只剩下一个方法:
public static Comparable min(Comparable[] a)
注意,类型参数T已经被擦除了,只留下了它的限定类型Comparable。
方法的擦除带来了一些复杂性。看下面这个示例:
public class DateInterval extends Pair<LocalDate> { public DateInterval(){} public DateInterval(LocalDate first, LocalDate second){ super(first, second); } public void setSecond(LocalDate second){ if (second.compareTo(getFirst()) >= 0){ super.setSecond(second); } }}
一个日期区间是一对LocalDate对象,并且想要在该类中覆盖父类的Pair<T>
的setSecond(T second)来确保第二个值永远不小于第一个值。这个类被擦除后变成:
public class DateInterval extends Pair { public DateInterval(){} public DateInterval(LocalDate first, LocalDate second){ super(first, second); } public void setSecond(LocalDate second){ if (second.compareTo(getFirst()) >= 0){ super.setSecond(second); } }}
也许令人惊讶的是,存在另一个从Pair继承的setSecond方法,即
public void setSecond(Object newValue){ second = newValue;}
这两个方法:
public void setSecond(LocalDate second)//想要覆盖的方法public void setSecond(Object newValue)//继承而来的方法
显然是不同的方法,因为它有不同类型的参数:Object和LocalDate。
出现setSecond(Object newValue)
方法原因是:因为编译阶段Pair<LocalDate>
已经被类型擦除为Pair了,其setSecond方法变成了setSecond(Object newValue)
,所以DateInterval中的setSecond(LocalDate second)
是无法覆盖父类的setSecond(Object newValue)
的。
考虑下面的语法序列:
DateInterval interval = new DateInterval(LocalDate.of(2017,1,1), LocalDate.of(2017,8,1));Pair<LocalDate> pair = interval;pair.setSecond(LocalDate.of(2017, 9, 1));
我们的期望是,setSecond的调用是多态的,并且最适合的方法被调用。即我们的期望是:pair.setSecond(LocalDate.of(2017, 9, 1))
调用的方法应该是DateInterval的setSecond(Object newValue),因为pair的类型为Pair<LocalDate>
且有方法setSecond(Object newValue)。但是由于类型擦除导致子类并没有覆盖掉父类方法Pair的setSecond(Object second)而出现了的两个DateInterval.setSecond方法,进而导致这里的pair.setSecond(LocalDate)调用了DateInterval的setSecond(LocalDate),从而干扰了多态。排除这种干扰的关键在于,要成功的覆盖的父类方法Pair的setSecond(Object second)。
要解决这个问题,编译器会自动在DateInterval类中生成一个桥方法(bridge method):
public void setSecond(Object second){ setSecond((LocalDate) second);}
这样,桥方法就覆盖了泛型父类Pair的setSecond(Object second)了。对于多态来说就没有问题了。
要想了解它的工作过程,请仔细地跟踪下面语句的执行:
pair.setSecond(LocalDate.of(2017, 9, 1));
变量pair已经声明为类型Pair<LocalDate>
,并且这个类型只有一个简单的方法叫setSecond,即setSecond(Object)。虚拟机用pair引用的对象调用这个方法。这个对象是DateInterval类型,因而将会调用DateInterval.setSecond(Object)方法。这个方法是合成的桥方法。该方法调用DateInterval.setSecond(LocalDate),这正是我们所期望的操作效果。
桥方法可能会变得十分奇怪。假设DateInterval类也覆盖了getSecond方法:
public LocalDate getSecond() { return (LocalDate) super.getSecond();}
由于需要桥方法来覆盖父类中的getSecond,编译器会自动在DateInterval中生成一个桥方法:
public Object getSecond(){ getSecond();}
在擦除的类型中,有两个getSecond方法:
LocalDate getSecond()//想要覆盖的方法Object getSecond()//编译器覆盖父类的方法产生的桥方法
不能这样编写Java代码。具有相同参数类型(它们都没有参数)的两个方法是不合理的。但是,在虚拟机中,用参数类型和返回类型指定一个方法。因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确处理这一情况。
注意:
桥方法不仅用于泛型类型,在一个方法覆盖另一个方法时可以指定一个更严格的返回类型。例如:
public class Employee implements Cloneable { public Employee clone() throws CloneNotSupportedException { ... }}
Object.clone和Employee.clone方法被说成是具有协变的返回类型(covariarant return types)。
实际上,Employee类有两个克隆方法:
Employee.clone()// 定义Object.clone() // 覆盖了Object.clone
合成的桥方法调用了新定义的方法。
总之,需要记住有关Java泛型转换的事实:
- 虚拟机中没有泛型,只有普通的类和方法。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成来保持多态。
- 为保持类型安全性,必要时插入强制类型转换。
- 泛型代码和虚拟机
- Java泛型代码和虚拟机
- 8.5 泛型代码和虚拟机
- 泛型和虚拟机
- java泛型程序设计——类型变量限定 + 泛型代码和虚拟机
- Java核心技术卷I:基础知识(原书第8版):12.5 泛型代码和虚拟机
- Java泛型解析(03):虚拟机执行泛型代码
- x86虚拟机设计之自引用和自修改代码
- 通过写代码和虚拟机命令截取网上数据
- Java虚拟机学习之 代码编译和执行的整个过程
- java虚拟机之代码编译和执行的过程
- java虚拟机学习之代码签名和认证
- 学习【深入理解java虚拟机】一 :泛型和擦除
- 反虚拟机代码
- 虚拟机实现代码
- Dalvik虚拟机和Art虚拟机
- 虚拟机管理和虚拟机快照
- .net平台和java虚拟机中托管代码的优点和区别
- google支付遇坑记录
- 导入js文件出现中文乱码
- 苹果手机使用技巧汇总,手把手教你如何快速使用苹果手机
- 【前端知识点】模块化开发介绍
- Sublime的安装、添加插件以及其的使用
- 泛型代码和虚拟机
- c# winform 获取当前程序运行根目录
- Postman发送Map参数
- [Azure]使用Powershell克隆ARM虚拟机(托管磁盘)
- C# byte数组转化位Bitmap
- 极大似然估计法推出朴素贝叶斯法中的先验概率估计公式
- Dinic 模板
- 【知了堂学习笔记】jfreechart创建简单的柱状图和饼图
- saxbuilder用法