一、各种语言中的编译器是如何处理泛型的
通常情况下,一个编译器处理泛型有两种方式:
1.Code specialization。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list,可能需要 针对string,integer,float产生三份目标代码。
2.Code sharing。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
C++ 中的模板(template)是典型的Code specialization实现。 C++ 编译器会为每一个泛型类实例生成一份执行代码。执行代码中integer list和string list是两种不同的类型。这样会导致 代码膨胀(code bloat) 。 C# 里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,List
C++和C#是使用Code specialization的处理机制,前面提到,他有一个缺点,那就是 会导致代码膨胀 。另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用Code sharing方式处理泛型的主要原因。
Java编译器通过Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过 类型擦除 (type erasue)实现的。
二、什么是类型擦除
前面我们多次提到这个词: 类型擦除 (type erasue)**,那么到底什么是类型擦除呢?
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。(这部分内容可以看:Java泛型中extends和super的理解) 2.移除所有的类型参数。
三、Java编译器处理泛型的过程
code 1:
public static void main(String[] args) {
Map
map.put("name", "hollis");
map.put("age", "22");
System.out.println(map.get("name"));
System.out.println(map.get("age"));
}
反编译后的code 1:
public static void main(String[] args) {
Map map = new HashMap();
map.put("name", "hollis");
map.put("age", "22");
System.out.println((String) map.get("name"));
System.out.println((String) map.get("age"));
}
我们发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型,
code 2:
interface Comparable {
public int compareTo(A that);
}
public final class NumericValue implements Comparable
private byte value;
public NumericValue(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
public int compareTo(NumericValue that) {
return this.value - that.value;
}
}
反编译后的code 2:
interface Comparable {
public int compareTo( Object that);
}
public final class NumericValue
implements Comparable
{
public NumericValue(byte value)
{
this.value = value;
}
public byte getValue()
{
return value;
}
public int compareTo(NumericValue that)
{
return value - that.value;
}
public volatile int compareTo(Object obj)
{
return compareTo((NumericValue)obj);
}
private byte value;
}
code 3:
public class Collections {
public static > A max(Collection xs) {
Iterator xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0)
w = x;
}
return w;
}
}
反编译后的code 3:
public class Collections
{
public Collections()
{
}
public static Comparable max(Collection xs)
{
Iterator xi = xs.iterator();
Comparable w = (Comparable)xi.next();
while(xi.hasNext())
{
Comparable x = (Comparable)xi.next();
if(w.compareTo(x) < 0)
w = x;
}
return w;
}
}
第2个泛型类Comparable 擦除后 A被替换为最左边界Object。Comparable
四、泛型带来的问题
一、当泛型遇到重载:
public class GenericTypes {
public static void method(List
System.out.println("invoke method(List
}
public static void method(List
System.out.println("invoke method(List
}
}
上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List
二、当泛型遇到catch:
如果我们自定义了一个泛型异常类GenericException ,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException 、GenericException ,这也是有问题的。
三、当泛型内包含静态变量
public class StaticTest{
public static void main(String[] args){
GT
gti.var=1;
GT
gts.var=2;
System.out.println(gti.var);
}
}
class GT
public static int var=0;
public void nothing(T x){}
}
答案是------2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
五、总结
虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在List
创建泛型对象时请指明类型,让编译器尽早的做参数检查( Effective Java,第23条:请不要在新代码中使用原生态类型 )
不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。
静态变量是被泛型类的所有实例所共享的。对于声明为MyClass
泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException
为了让学习变得轻松、高效,今天给大家免费分享一套Java教学资源。帮助大家在成为Java架构师的道路上披荆斩棘。需要资料的欢迎加入学习交流群:9285,05736