解析java泛型的的类型擦除_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 解析java泛型的的类型擦除

解析java泛型的的类型擦除

 2013/7/28 21:13:19  _Yggd  程序员俱乐部  我要评论(0)
  • 摘要:解析java泛型的的类型擦除一、Java泛型的本质Java虚拟机中并没有泛型类型对象,所有的对象都是一样的,都属于普通的类。由于JVM根本不支持泛型类型,是编译器?耍了个花招?,使得似乎存在对泛型类型的支持,它们用泛型类型信息检查所有的代码,但随即?擦除?所有的泛型类型并生成只包含普通类型的类文件。C#泛型实现的是类型膨胀,即真实泛型,C#里面泛型无论在程序源码中、编译后的IL中或是运行期的CLR中都是切实存在的,List<Integer>与List<String>
  • 标签:Java 泛型 解析
解析java泛型的的类型擦除
一、Java 泛型的本质
Java 虚拟机中并没有泛型类型对象,所有的对象都是一样的,都属于普通的类。由于 JVM 根本不支持泛型类型,是编译器?耍了个花招?,使得似乎存在对泛型类型的支持,它们用泛型类型信息检查所有的代码,但随即?擦除?所有的泛型类型并生成只包含普通类型的类文件。C#泛型实现的是类型膨胀,即真实泛型,C#里面泛型无论在程序源码中、编译后的 IL 中或是运行期的 CLR 中都是切实存在的,List<Integer>与 List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据。而 Java 语言中的泛型则不一样,实现的是类型擦除,它只在程序源码中存在,在编译后的字节码文件中,就已经被替
换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的 Java 语言来说,ArrayList<Integer>与 ArrayList<String>就是同一个类ArrayList,Integer 和 String 被擦除了。如:List<Integer> integer = new ArrayList<Integer>();
List<String> string = new ArrayList<String>(); JVM 会去解析泛型语法实现擦除,擦除后语句变成了原始类型:
List integer = new ArrayList();
List string = new ArrayList();
ArrayList<Integer>和 ArrayList<String>类型的实例是相
同的类,如: integer.getClass() = =string.getClass() 编译器只为 ArrayList 生成一个类。所以说 Java 泛型技术实际上是 Java 语言的一个迷惑,JVM 对泛型的编译没有真正使用泛
型的标准原理来实现。

二、类型擦除原则
正确理解泛型概念的首要前提是理解类型擦除,擦除不是一个语言特性,它是 Java 的泛型实现中的一种折中,因为泛型不是Java 语言出现时就有的组成部分,所以这种折中是必需的,目的是为了支持向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义。正因为泛型实现了类型擦除,因此泛型类型不能在某些重要的上下文环境中使用,而只能在静态类型检查期间才出现,并在此之后,程序中的所有泛型类型都将被擦除,
替换为相应的非泛型。泛型擦除的基本过程体现为以下几个原则:
(1)所有参数化容器类都被擦除成非参数化的(raw type);
如 List<E>、List<List<E>>都被擦除成 List;
(2)所有参数化数组都被擦除成非参数化的数组;如
List<E>[],被擦除成 List[];
(3)Raw type 的容器类,被擦除成其自身,如 List<E>被擦
除成 List;
(4)原生类型(int,String 还有 wrapper 类)都擦除成他
们的自身;

(5)参数类型 E,如果没有上限,则被擦除成 Object;
(6)所有约束参数如<? Extends E>、<X extends E>都被擦
除成 E;
(7)如果有多个约束,擦除成第一个,如<T extends Object
& E>,则擦除成 Object;
由于擦除的实现,减少了泛型的泛化性,所有的强制转换都
为自动和隐式的,提高了代码的重用率。但擦除机制也导致了一
些有用的类型信息丢失,因此在使用时,泛型存在一些约束和限
制。


三、擦除产生的问题
擦除是一种很巧妙的办法,但它有时候会带来一些意想不到
错误:两个看上去并不相同的泛型类或是泛型方法,由于擦除
的作用,最后会得到相同的类和方法。这种错误,也被称为冲突。
冲突主要发生在下述三种情况。

(一)静态成员共享问题
List<Integer> in= Arrays.asList(1,2,3); 
List<String> str = Arrays.asList("one","two");
assert in.getClass() == str.getClass(); 
in 和 str 两个对象最终被擦除成具有相同类型的(List)的对象,于是这两个对象共享 List 的静态成员,于是就可以得出这样的结论,所有泛化类型的静态成员被其所有的实例化对象共享,因此也就要求所有静态成员不能够是泛化的。 class Coo<T> {
static T value1;     //错误
private T value2;     //正确
private static List<T> value3 = new ArrayList<T>(); //错误
public T getValue() 
{ return value; }
public static List<Object> value4 = new ArrayList<Object>()
//正确
}

出现错误的两个变量 value1 和 value3 都采用不同的形式使用了类型参数 T。由于它们是静态成员,是独立于任何对象的,也可以在对象创建之前就被使用。此时,编译器无法知道用哪一个具体的类型来替代 T,所以编译器不允许这样使用。 (二)重载冲突问题
在一个类的范围内,如果两个函数具有相同的函数名称,不同的参数(返回值不考虑)就互相称为重载函数。擦除带来的另外一个问题是重载的冲突,下面是两个方法的重载:
void conflict(T o){}
void conflict(Object o){}
由于在编译时,T 会被 Object 所取代,所以这两个实际上声
明的是同一个方法,重载就出错了。

(三)接口实现问题
由于接口也可以是泛型接口,而一个类又可以实现多个泛型接口,所以也可能会引发冲突。
class Coo
implements
Comparable<Integer>,
Comparable<Long>
由于 Comparable<Integer>、Comparable<Long>都被擦除成
Comparable,所以这实际上是实现的同一个接口。如:
class Coo<T> implements Comparable<T>
如果要实现多个泛型接口,只能实现具有不同擦除效果的接口。

四、类型擦除的限制
因为 Java 并没有真正实现泛型,而采用擦除机制,造成了Java 泛型本身有很多漏洞。为了规避这些问题,Java 在泛型的使用上除了对上述三种冲突进行约束外,还做了其它的限制。下面是其中一些限制:
(一)不能用泛型类型参数创建实例对象,如:E object=new
E();。因为由于类型擦除,编译时所有泛型类型都会相应的变
为它的原始类型,这时泛型 E 是不可用的。 
(二)不能在数据类型转换instanceof 操作中使用类型参
数。
(三)不能用基本类型替换类型参数,如:List<int>。基本
类型不属于对象,类型擦除后不能被 Object 或者限定类型替换,
但如果要将基本类型应用到泛型当中,可以用基本类型的包装类。
(四)不能将泛型用在异常捕获和用泛型来实现 Throwable
子类,如:try{}catch (MyException<ex>){}。因为泛型不能扩
展 java.lang.Throwable,这样做将出现编译错误。
(五)不能使用泛型类创建数组。如:ArrayList<String>[]
list=new ArrayList<String>[10];。因为如果先创建一个类型数
组而后转型为 Object 或是先声明类型数组而后创建一个 Object
数组通过强制类型转换来实现类型数组都将导致失败或者是类型
擦除方面的问题。 
总结:
泛型是 Java 语言走向类型安全的一大步,它提升了 Java 代
码的健壮性和易用性,但 SUN 本身过分强调向前的兼容性,引入
了擦除,由此产生了不少问题和麻烦。本文重点让大家认识类型
擦除的本质,了解擦除产生的问题及解决方法,旨在帮助大家更
深入的学习泛型。
发表评论
用户名: 匿名