Java核心技术第12章(3)
来源:互联网 发布:ubuntu luvit 编辑:程序博客网 时间:2024/06/05 06:27
12.5 泛型代码和虚拟机
虚拟机没有泛型类型对象--所有对象都属于普通类.无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type).原始类型的名字就是删除类型参数后的泛型类型名.擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object).例如,Pair<T>的原始类型如下所示:
public class Pair{ private Object first; private Object second; public Pair(Object first, Object second) { this.first = first; this.second = second; } ...}因为T是一个无限定的变量,所以直接用Object替换.
在程序中可以包含不同类型的Pair,例如,Pair<String>或Pair<GregorianCalendar> .而擦除类型后就变成原始的Pair类型了.
注释:就这点而言,Java泛型与C++模板有很大的区别.C++中每个模板的实例化产生不同的类型,这一现象称为"模板化膨胀",Java不存在这个问题的困扰.
原始类型用第一个限定的类型变量来替换,如果没有给定限定就用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 = second; } }}原始类型Interval如下所示:
public class Interval implements Serializable{ private Comparable lower; private Comparable upper; ... public Interval(Comparable first, Comparable second) {...}}
12.5.1 翻译泛型表达式
当程序调用泛型方法时,如果擦掉返回类型,编译器插入强制类型转换.例如,下面这个语句序列Pair<Employee> buddies = ...;Employee buddy = buddies.getFirst();擦除getFirst的返回类型后将返回Object类型.编译器自动插入Employee的强制类型转换.也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
对原始方法Pair.getFirst的调用
将返回的Object类型强制转换为Employee类型
当存取一个泛型域时也要插入强制类型转换.假设Pair类的first域和second域都是公有的.表达式:
Employee buddy = buddies.first;也会在结果字节码中插入强制类型转换.
12.5.2 翻译泛型方法
类型擦除也会出现在泛型方法中.程序员通常认为下述的泛型方法public static <T extends Comparable> T min(T[] a)是一个完整的方法族,而擦除类型之后,只剩下一个方法:
public static Comparable min(Comparable[] a)注意,类型参数T已经被擦除掉了,只留下限定类型Comparable .
方法的擦除带来了两个复杂问题,看一看下面这个示例:
class DateInterval extends Pair<Date>{ public void setSecond(Date second) { if (second.compareTo(getFirst()) >= 0) super.setSecond(second); } ...}一个日期区间是一对Date对象,并且需要覆盖这个方法来确保第二个值永远不小于第一个值.这个类擦除后变成
class DateInterval extends Pair{ public void setSecond(Date second) { ... }}令人感到奇怪的是,存在另一个从Pair继承的setSecond方法,即
public void setSecond(Object second)这显然是一个不同的方法,因为它有一个不同类型的参数--Object,而不是Date .然而,不应该不一样.考虑下面的语句序列:
DateInterval interval = new DateInterval(...);Pair<Date> pair = interval;pair.setSecond(aDate);这里,希望对setSecond的调用具有多态性,并调用最合适的那个方法.由于pair引用DateInterval对象,所以应该调用DateInterval.setSecond,问题在于类型擦除与多态性发生了冲突.要解决这个问题,就需要编译器在DateInterval类中生成一个桥方法(bridge method):
public void setSecond(Object second) { setSecond((Date)second); }要想了解它的工作过程,请仔细地跟踪下列语句的执行:
pair.setSecond(aDate)变量pair已经声明为类型Pair<Date>,并且这个类型只有一个简单的方法叫setSecond,即setSecond(Object) .虚拟机用pair引用的对象调用这个方法.这个对象是DateInterval类型的,因而将会调用DateInterval.setSecond(Object)方法.这个方法是合成的桥方法,它调用DateInterval.setSecond(Date),这正是所期望的调用效果.
桥方法可能变得非常奇怪,假设DateInterval方法也覆盖了getSecond方法:
class DateInterval extends Pair<Date>{ public Date getSecond() { return (Date)super.getSecond().clone();} ...}在擦除的类型中,有两个getSecond方法:
Date getSecond(); // define in DateIntervalObject getSecond(); // overrides the method defined in Pair to call the first method不能这样编写Java代码,它们都没有参数,但是,在虚拟机中,用参数类型和返回类型确定一个方法.因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能能够正确地处理这一情况.
注释:桥方法不仅用于泛型类型.在一个方法覆盖另一个方法时可指定一个更严格的返回类型.例如:
public class Employee implements Cloneable{ public Employee clone() throws CloneNotSupportedException { ... }}Object.clone和Employee.clone方法被说成具有协变的返回类型(covariant return types).
实际上,Employee类有两个克隆方法:
Employee clone(); // defined aboveObject clone(); // synthesized bridge method, overrides object.clone合成的桥方法调用了新定义的方法.
总之,需要记住有关Java泛型转换的事实:
虚拟机中没有泛型,只有普通的类和方法.
所有的类型参数都用它们的限定类型替换.
桥方法被合成来保持多态.
为保持类型安全性,必要时插入强制类型转换.
12.6 约束与局限性
在下面几节中,将阐述使用Java泛型时需要考虑的一些限制.大多数限制都是由类型擦除引起的.12.6.1 不能用基本类型实例化类型参数
不能用类型参数代替基本类型,因此,没有Pair<double>,只有Pair<Double>.当然,其原因是类型擦除.擦除之后,Pair类含有Object类型的域,而Object不能存储 double 值.这的确令人烦恼.但是,这样做与Java语言中基本类型的独立状态相一致.这并不是一个致命的缺陷--只有8种基本类型,当包装器类型不能接受替换时,可以使用独立的类和方法处理它们.
12.6.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型.因此,所有的类型查询只产生原始类型.例如:if (a instanceof Pair<String>) // error实际上仅仅测试a是否是任意类型的一个Pair .下面的测试同样如此:
if (a instanceof Pair<T>) // error或强制类型转换:
Pair<String> p = (Pair<String>)a;要记住这一风险,无论何时使用 instanceof 或涉及泛型类型的强制类型转换表达式都会看到一个编译器警告.
同样的道理,getClass方法总是返回原始类型.例如:
Pair<String> stringPair = ...;Pair<Employee> employeePair = ...;if (stringPair.getClass() == employeePair.getClass()) // they are equal其比较的结果是 true,这是因为两次调用getClass都将返回Pair.class .
12.6.3 不能创建参数化类型的数组
不能实例化参数化类型的数组,例如:Pair<String>[] table = new Pair<String>[10]; // error擦除之后,table的类型是Pair[],可以把它转换为Object[].
Object[] objarray = table;数组会记住它的元素类型,如果试图存储其他类型的元素,就会抛出一个ArrayStoreException异常:
objarray[0] = "hello";不过对于泛型类型,擦除会使得这种机制无效,以下赋值:
objarray[0] = new Pair<Employee>();能够通过数组存储检查,不过仍然会导致一个类型错误.出于这个原因,不允许创建参数化类型的数组.
需要说明的是,只是不允许创建这些数组,而声明类型为Pair<String>[]的变量是合法的.
12.6.5 不能实例化类型变量
不能使用像 new T(...),new T[...]或T.class这样的表达式中的类型变量.例如下面的Pair<T>构造器就是非法的:public Pair() { first = new T(); second = new T(); } // error类型擦除将T改变为Object,而且本意肯定不希望调用 new Object().
12.7 泛型类型的继承规则
无论S与T有什么联系(甚至是父类与子类的关系),通常,Pair<S>与Pair<T>没有什么联系. 0 0
- Java核心技术第12章(3)
- Java核心技术第12章(1)
- Java核心技术第12章(2)
- Java核心技术第12章(4)
- Java核心技术第12章(4)
- Java核心技术第12章(4)
- Java核心技术笔记-第12章
- Java核心技术第3章(3)
- Java-核心技术总结-第3章
- Java核心技术第3章(1)
- Java核心技术第3章(2)
- Java核心技术第3章(4)
- Java核心技术第3章(5)
- Java核心技术第3章(6)
- Java核心技术第3章(7)
- Java核心技术第3章(8)
- Java核心技术第4章(3)
- Java核心技术第5章(3)
- Database Cloud Service试用二(数据导入)
- Java实现crc16校验 附上校验工具对照。解决长数据校验不正确的问题
- Matlab暂停语句
- Java笔记集合
- Java F-bounded
- Java核心技术第12章(3)
- Matlab回显语句
- rpm包安装gcc报错
- Step7中有关时间和定时器的使用和例程2
- linux的ulimit限制详解
- Java动态代理
- sqlserver2008完全卸载 转载自百度经验
- c++ new和delete产生的debug error
- Android 项目中执行java的main方法