没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
Java 的泛型是伪泛型。为什么说 Java 的泛型是伪泛型呢?因为,在编译期
间,所有的泛型信息都会被擦除掉。
Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节
码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编
译器在编译的时候去掉。这个过程就称为类型擦除。
如在代码中定义的 List<object>和 List<String>等类型,在编译后都会
编程 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不
可见的。Java 编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法
避免在运行时刻出现类型转换异常的情况。类型擦除也是 Java 的泛型实现方法
与 C++模版机制实现方式之间的重要区别。
可以通过两个简单的例子,来证明 java 泛型的类型擦除。
例 1、
[java]view plain copy
1. publicclassTest4{
2. publicstaticvoidmain(String[]args){
3. ArrayList<String>arrayList1=newArrayList<String>();
4. arrayList1.add("abc");
5. ArrayList<Integer>arrayList2=newArrayList<Integer>();
6. arrayList2.add(123);
7. System.out.println(arrayList1.getClass()==arrayList2.getClass());
8. }
9. }
在这个例子中,我们定义了两个 ArrayList 数组,不过一个是
ArrayList<String>泛型类型,只能存储字符串。一个是
ArrayList<Integer>泛型类型,只能存储整型。最后,我们通过 arrayList1
对象和 arrayList2 对象的 getClass 方法获取它们的类的信息,最后发现结果
为 true。说明泛型类型 String 和 Integer 都被擦除掉了,只剩下了原始类型。
例 2、
[java]view plain copy
1. publicclassTest4{
2. publicstaticvoidmain(String[]args)throwsIllegalArgumentException,Secur
ityException,IllegalAccessException,InvocationTargetException,NoSuchMethod
Exception{
3. ArrayList<Integer>arrayList3=newArrayList<Integer>();
4. arrayList3.add(1);//这样调用 add 方法只能存储整形,因为泛型类型的实例为
Integer
5. arrayList3.getClass().getMethod("add",Object.class).invoke(arrayList3,"as
d");
6. for(inti=0;i<arrayList3.size();i++){
7. System.out.println(arrayList3.get(i));
8. }
9. }
在程序中定义了一个 ArrayList 泛型类型实例化为 Integer 的对象,如果直
接调用 add 方法,那么只能存储整形的数据。不过当我们利用反射调用 add
方法的时候,却可以存储字符串。这说明了 Integer 泛型实例在编译之后被擦
除了,只保留了原始类型。
编译器如何处理泛型?
通常情况下,一个编译器处理泛型有两种方式:
1.Code specialization。在实例化一个泛型类或泛型方法时都产生一份
新的目标代码(字节码 or 二进制代码)。例如,针对一个泛型 list,可能需要
针对 string,integer,"oat 产生三份目标代码。
2.Code sharing。对每个泛型类只生成唯一的一份目标代码;该
泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型
检查和类型转换。
C++中的模板(template)是典型的 Code specialization 实现。C++
编译器会为每一个泛型类实例生成一份执行代码。执行代码中 integer list 和
string list 是两种不同的类型。这样会导致代码膨胀(code bloat),不过有
经验的 C++程序员可以有技巧的避免代码膨胀。
Code specialization 另外一个弊端是在引用类型系统中,浪费空间,因
为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执
行代码。而这也是 Java 编译器中采用 Code sharing 方式处理泛型的主要原
因。
Java 编译器通过 Code sharing 方式为每个泛型类型创建唯一的字节码表
示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型
类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。
类型擦除后保留的原始类型
在上面,两次提到了原始类型,什么是原始类型?原始类型(raw type)
就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定
义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除
(crased),并使用其限定类型(无限定的变量用 Object)替换。
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。
编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型
擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添
加类型检查和类型转换的方法。类型擦除可以简单的理解为将泛型 java
代码转换为普通 java 代码,只不过编译器更直接点,将泛型 java 代
码直接转换成普通 java 字节码。
类型擦除的主要过程如下:
1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
2.移除所有的类型参数。
如果类型变量有限定,那么原始类型就用第一个边界的类型变量来替换。比
如 Pair 这样声明
例 4:
[java]view plain copy
1. publicclassPair<TextendsComparable&Serializable>{
那么原始类型就是 Comparable
注意:
如果 Pair 这样声明 public class Pair<T extends Serializable&Comparable> ,
那么原始类型就用 Serializable 替换,而编译器在必要的时要向 Comparable 插
入强制类型转换。为了提高效率,应该将标签(tagging)接口(即没有方法的
接口)放在边界限定列表的末尾。
要区分原始类型和泛型变量的类型
在调用泛型方法的时候,可以指定泛型,也可以不指定泛型。
在不指定泛型的情况下,泛型变量的类型为 该方法中的几种类型的同一个父类
的最小级,直到 Object。
在指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类。
[java]view plain copy
1. publicclassTest2{
2. publicstaticvoidmain(String[]args){
3. /**不指定泛型的时候*/
4. inti=Test2.add(1,2);//这两个参数都是 Integer,所以 T 为 Integer 类
型
5. Numberf=Test2.add(1,1.2);//这两个参数一个是 Integer,以风格是
Float,所以取同一父类的最小级,为 Number
6. Objecto=Test2.add(1,"asd");//这两个参数一个是 Integer,以风格是
Float,所以取同一父类的最小级,为 Object
7.
8. /**指定泛型的时候*/
剩余19页未读,继续阅读
资源评论
- ouhaibo1232014-12-02终于解开了我心中的疑惑
xulianzhen
- 粉丝: 4
- 资源: 30
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功