Java中的泛型和类型擦除
2016-05-05 09:59
316 查看
整理自http://irfen.iteye.com/blog/1888312
/article/2232746.html
/article/4582643.html
Code specialization。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要 针对string,integer,float产生三份目标代码。
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)实现的。
类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
这个例子证明Java泛型的类型擦除在编译的时候去掉,只剩下原始类型,默认用Object替换
可以用这个语句来限定变量的原始类型,此时原始类型就用Serializable替换,而编译器在必要的时要向Comparable插入强制类型转换。
先检查,在编译,以及检查编译的对象和引用传递的问题
既然说类型变量会在编译的时候擦除掉,那为什么我们往ArrayList arrayList=new ArrayList();所创建的数组列表arrayList中,不能使用add方法添加整形呢?不是说泛型变量Integer会在编译时候擦除变为原始类型Object吗,为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
java是如何解决这个问题的呢?java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。
在上面的程序中,使用add方法添加一个整形,在eclipse中,直接就会报错,说明这就是在编译之前的检查。因为如果是在编译之后检查,类型擦除后,原始类型为Object,是应该运行任意引用类型的添加的。可实际上却不是这样,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。
类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
本来类型检查就是编译时完成的。new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用,因为我们是使用它引用arrayList1 来调用它的方法,比如说调用add()方法。所以arrayList1引用能完成泛型类型的检查。
而引用arrayList2没有使用泛型,所以不行。
泛型中参数化类型不考虑继承关系
都是不允许的,第一个,它里面实际上已经被我们存放了Object类型的对象,这样,就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。
第二种情况,则是存放String的实例,用存放Object的引用指向它,最起码,在我们用arrayList2取值的时候不会出现ClassCastException,因为是从String转换为Object。可是,这样做有什么意义呢,泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以java不允许这么干。
自动类型转换 在你调用的地方进行checkcast操作
类型擦除与多态
可是由于种种原因,虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。那我们怎么去重写我们想要的Date类型参数的方法?于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法
泛型类型变量不能是基本数据类型
运行时类型查询
类型通配符
类型通配符上限通过形如Box< ? extends Number>形式定义,相对应的,类型通配符下限为Box< ? super Number>形式,其含义与类型通配符上限正好相反
/article/2232746.html
/article/4582643.html
泛型的实现
通常情况下,一个编译器处理泛型有两种方式:Code specialization。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要 针对string,integer,float产生三份目标代码。
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)实现的。
类型擦除
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List和List等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
public class Test { public static void main(String[] args) { ArrayList<String> arrayList1=new ArrayList<String>(); arrayList1.add("abc"); ArrayList<Integer> arrayList2=new ArrayList<Integer>(); arrayList2.add(123); System.out.println(arrayList1.getClass()==arrayList2.getClass()); } }
这个例子证明Java泛型的类型擦除在编译的时候去掉,只剩下原始类型,默认用Object替换
public class Pair<T extends Comparable& Serializable> { ... }
可以用这个语句来限定变量的原始类型,此时原始类型就用Serializable替换,而编译器在必要的时要向Comparable插入强制类型转换。
类型擦除引起的问题及解决方法
因为种种原因,Java不能实现真正的泛型,只能使用类型擦除来实现伪泛型,这样虽然不会有类型膨胀的问题,但是也引起了许多新的问题。所以,Sun对这些问题作出了许多限制,避免我们犯各种错误。先检查,在编译,以及检查编译的对象和引用传递的问题
既然说类型变量会在编译的时候擦除掉,那为什么我们往ArrayList arrayList=new ArrayList();所创建的数组列表arrayList中,不能使用add方法添加整形呢?不是说泛型变量Integer会在编译时候擦除变为原始类型Object吗,为什么不能存别的类型呢?既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
java是如何解决这个问题的呢?java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。
public static void main(String[] args) { ArrayList<String> arrayList=new ArrayList<String>(); arrayList.add("123"); arrayList.add(123);//编译错误 }
在上面的程序中,使用add方法添加一个整形,在eclipse中,直接就会报错,说明这就是在编译之前的检查。因为如果是在编译之后检查,类型擦除后,原始类型为Object,是应该运行任意引用类型的添加的。可实际上却不是这样,这恰恰说明了关于泛型变量的使用,是会在编译之前检查的。
类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
public class Test { public static void main(String[] args) { // ArrayList<String> arrayList1=new ArrayList(); arrayList1.add("1");//编译通过 arrayList1.add(1);//编译错误 String str1=arrayList1.get(0);//返回类型就是String ArrayList arrayList2=new ArrayList<String>(); arrayList2.add("1");//编译通过 arrayList2.add(1);//编译通过 Object object=arrayList2.get(0);//返回类型就是Object new ArrayList<String>().add("11");//编译通过 new ArrayList<String>().add(22);//编译错误 String string=new ArrayList<String>().get(0);//返回类型就是String } }
本来类型检查就是编译时完成的。new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用,因为我们是使用它引用arrayList1 来调用它的方法,比如说调用add()方法。所以arrayList1引用能完成泛型类型的检查。
而引用arrayList2没有使用泛型,所以不行。
泛型中参数化类型不考虑继承关系
ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误 ArrayList<Object> arrayList1=new ArrayList<String>();//编译错误
都是不允许的,第一个,它里面实际上已经被我们存放了Object类型的对象,这样,就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。
第二种情况,则是存放String的实例,用存放Object的引用指向它,最起码,在我们用arrayList2取值的时候不会出现ClassCastException,因为是从String转换为Object。可是,这样做有什么意义呢,泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以java不允许这么干。
自动类型转换 在你调用的地方进行checkcast操作
类型擦除与多态
class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } class DateInter extends Pair<Date> { @Override public void setValue(Date value) { super.setValue(value); } @Override public Date getValue() { return super.getValue(); } } //类型擦除后 class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } @Override public void setValue(Date value) { super.setValue(value); } @Override public Date getValue() { return super.getValue(); }
可是由于种种原因,虚拟机并不能将泛型类型变为Date,只能将类型擦除掉,变为原始类型Object。这样,我们的本意是进行重写,实现多态。可是类型擦除后,只能变为了重载。那我们怎么去重写我们想要的Date类型参数的方法?于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法
泛型类型变量不能是基本数据类型
运行时类型查询
if( arrayList instanceof ArrayList<?>)
类型通配符
类型通配符上限通过形如Box< ? extends Number>形式定义,相对应的,类型通配符下限为Box< ? super Number>形式,其含义与类型通配符上限正好相反
相关文章推荐
- Java集合浅谈
- android笔记之Eclipse ADT的 keystore证书规格及修改证书密码别名
- 使用IntelliJ IDEA开发SpringMVC网站(二)框架配置
- Java 垃圾清理基础理论
- Java 正则表达式详解
- Javaweb学习之关于分页
- 使用IntelliJ IDEA开发SpringMVC网站(一)开发环境
- windows Java编写hadoop程序
- java中的getter()和setter()
- Eclipse 快捷键 篇
- Javaweb学习之JSP基础
- Java中的基础----编程规则,开发原则,面向对象设计原则、面向对象的特征
- 【深入理解JVM】:Java内存区域
- 学习总结 JAVA环境配置 及其相应的步骤
- 实战Java虚拟机图片
- Struts2文件上传--多文件上传(插件uploadify)
- 跟Tom学后台开发--Java--入门代码 oschina-git
- JAVA正则表达式语法大全
- spring 通用异常处理,ajax异常返回json
- 深度分析 Java 的 ClassLoader 机制(源码级别)