java学习之路-泛型程序的类型擦除以及影响
对于泛型程序设计,类型擦除比较重要同时这部分较难理解。
参考书籍 java核心卷1 ‘java编程思想‘
下面将讨论几个关键点
1.什么是类型擦除?
2 为什么虚拟机要进行类型擦除?
3 因为擦除,编译器自动做的工作。
4 类型擦除对使用java泛型带来的约束和局限性。
1 什么是类型擦除?
类型擦除意味着你在使用泛型时候,任何具体的类型信息都被擦除掉了。你唯一知道的是你在使用一个对象。例子:
List<String>和List<Integer>
在运行时事实上是相同的 类型。因为这两种类型在运行的时候都被擦除成它们的原生类型。
那么,什么叫原生类型呢?
其实不管在定义什么类型的泛型,都会自动提供一个原生类型。原生类型的名字就是删除类型参数后的泛型类型名。原生类型可以用泛型类型的第一个限定的类型变量替换,如果没有给定限定就用Object替换。
例子:
//没有限定类型的类型变量
public class Pair<T>{
private T first;
private T second;
...
}
//泛型擦除后,用object替换
public class Pair{
private Object first;
private Object second;
...
}
----------
//有限定类型的类型变量
public class Pair<T extends Comparable>{
private T first;
private T second;
...
}
//可以用第一个限定类型变量替换泛型类型
public class Pair{
private Comparable first;
private Comparable second;
...
}
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
2 为什么要进行擦除?
- 首先要明白,在java1.0中并没有泛型的概念,泛型是后来引入的。如果java语言一开始就有泛型。那么就可以使类型参数保持为第一类实体,你就可以字类型参数 上执行基于类型的语言操作和反射操作。
- 接着要明白一个概念就是迁移兼容性。擦除的核心动机就是它使得泛化的客户端可以用非泛化的类库来使用。
- 因为javase5之前编写的类库都是非泛型类库。但是现在有的类库是泛型。要想实现泛型与非泛型的类库共存,而且当某个类库变成泛型时候,不会破坏依赖于它的代码和应用程序。java设计者们就想到了利用擦除,来实现非泛型向着泛型的迁移。
- 擦除的主要作用是:从非泛型代码到泛型代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入java语言。
3 因为擦除,神奇编译器的工作
编译器自动翻译泛型表达式
当程序调用泛型方法时候。因为擦除返回类型,编译器会自动插入强制类型转换。
什么意思呢?见例子
Pair<String> s = ...;
String s1 = s.getFirst();
//其实因为类型擦除,s.getFirst()的返回值应该是 Object类型,但是编译器会自动进行强制转换类型,即:
String s1 = (String)s.getFirst();
//编译器在调用这个方法的时候其实可以翻译成两条虚拟机的指令
1。对原始方法s.getFirst()的调用。
2。将返回的Object值强制转换成String类型。
编译器自动翻译泛型方法
类型擦除也会作用于泛型方法。
方法泛型擦除带来的问题
public class DateInterval extends Pair<LocalDate>{
public void setSecond(LocalDate second){
....}
}
//类型擦除之后
public class DateInterval extends Pair{
public void setSecond(LocalDate second){
....}
}
在上段代码中, Pair的泛型类型擦除,所有DateInterval类其实会从pair中继承一个方法
public void setSecond(Object second){
....}
显然setSecond 是两个不用的方法,因为它们有不同的参数。那么问题来了,见下面代码:
DateInterval di = new DateInterval();
Pair<LocalDate> pair = di;
pair.setSecond(a);// 问题是:pair.setSecond(a);调用的到底是那个方法呢?
在这里,希望对setSecond(a)的调用具有多态性,但是pair其实是Pair<LocalDate>
类型,但是该变量引用DateInterval类型,所以应该是调用setSecond(LocalDate second)方法,但是因为类型擦除,Pair类中只有setSecond(Object second)方法,那么pair就会调用DateInterval.setSecond(Object second)方法,这样就不能实现多态性。
解决办法:因为擦除跟多态行发生了冲突,所有编译器会自动生成桥方法。
//桥方法 重写DateInterval.setSecond(Object second)方法
//在改方法中引用DateInterval.setSecond(LocalDate second)
public void setSecond(Object second){
setSecond((Date) second);
....}
需要注意有关java泛型转换的事实
- 虚拟机中没有泛型,只有普通的类和方法。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成来保持多态
- 为了保持类型安全性,必要时插入强制类型转换。
4 擦除对java泛型带来的约束与局限性
未完待续。。重要点。。 看java-泛型程序设计(三)