Java中的泛型代码和C++中的模板有一个很大的不同:C++中模板的实例化会为每一种类型都产生一套不同的代码,这就是所谓的代码膨胀。
Java中并不会产生这个问题。虚拟机中并没有泛型类型对象,所有的对象都是普通类。
虚拟机中的泛型转换需要记住4条事实:
1) 定义任何一个泛型都会自动产生其原始类型(raw type)
2) 这个过程中,泛型类型会被擦除,替换为其限定类型(bounding type)
3) 必要时插入强制转换来保证类型安全
4) 使用桥接方法(bridge method)来保证正确处理多态
例如写一个返回数组中最小值的泛型函数,可以如下定义。
public static <T extends Comparable> T min(T[] a)
类型擦除后,替换为限定类型Comparable,其原始类型就是
public static Comparable min(Comparable[] a)
如果没有限定类型,那么类型擦除后就替换为Object。例如一个Pair<T>类
Pair<T>类执行类型擦除后的原始类型就是
下面一段代码,
Pair<String> pair = new Pair<String>("1", "2");
String s = pair.getFirst();
类型擦除后,getFirst()返回Object。所以,编译器实际上把它转换为下面的虚拟机指令:
a) 调用原始类型的Pair.getFirst
b) 把返回的Object对象强制转换为String
类型擦除有时候会带来很复杂的情况:
例如下面写了一个新的类:
类型擦除后,DateInterval中就有两个setSecond方法。
public void setSecond(Object second)
public void setSecond(Date second)
在这里,类型擦除和多态就产生了冲突。因为实际上我们是想要覆盖基类的方法。
为了解决这类问题,编译器会产生一个桥接方法
public void setSecond(Object second) {
setSecond((Date) second);
}
类型擦除后,DateInterval中有两个getSecond方法
public Date getSecond()
public Object getSecond()
在Java代码中,直接这么申明两个方法,是会导致编译错误的,因为不允许两个同名方法具有相同的参数类型。
但是,在虚拟机中,也就是编译后的代码中,参数类型加上返回类型来识别一个方法,所以这样做是允许的,因为返回类型不一样。
实际上,编译器生成这样一个桥接方法。
public Object getSecond() {
return getSecond();
}
Java中并不会产生这个问题。虚拟机中并没有泛型类型对象,所有的对象都是普通类。
虚拟机中的泛型转换需要记住4条事实:
1) 定义任何一个泛型都会自动产生其原始类型(raw type)
2) 这个过程中,泛型类型会被擦除,替换为其限定类型(bounding type)
3) 必要时插入强制转换来保证类型安全
4) 使用桥接方法(bridge method)来保证正确处理多态
例如写一个返回数组中最小值的泛型函数,可以如下定义。
public static <T extends Comparable> T min(T[] a)
(实际上Comparable也是一个泛型接口,所以最正确的定义方法是public static <T extends Comparable<? super T>> T min(T[] a) )
类型擦除后,替换为限定类型Comparable,其原始类型就是
public static Comparable min(Comparable[] a)
如果没有限定类型,那么类型擦除后就替换为Object。例如一个Pair<T>类
Pair<T>类执行类型擦除后的原始类型就是
下面一段代码,
Pair<String> pair = new Pair<String>("1", "2");
String s = pair.getFirst();
类型擦除后,getFirst()返回Object。所以,编译器实际上把它转换为下面的虚拟机指令:
a) 调用原始类型的Pair.getFirst
b) 把返回的Object对象强制转换为String
类型擦除有时候会带来很复杂的情况:
例如下面写了一个新的类:
类型擦除后,DateInterval中就有两个setSecond方法。
public void setSecond(Object second)
public void setSecond(Date second)
在这里,类型擦除和多态就产生了冲突。因为实际上我们是想要覆盖基类的方法。
为了解决这类问题,编译器会产生一个桥接方法
public void setSecond(Object second) {
setSecond((Date) second);
}
类型擦除后,DateInterval中有两个getSecond方法
public Date getSecond()
public Object getSecond()
在Java代码中,直接这么申明两个方法,是会导致编译错误的,因为不允许两个同名方法具有相同的参数类型。
但是,在虚拟机中,也就是编译后的代码中,参数类型加上返回类型来识别一个方法,所以这样做是允许的,因为返回类型不一样。
实际上,编译器生成这样一个桥接方法。
public Object getSecond() {
return getSecond();
}
可以使用Jad工具来反编译class文件,就能清除到看到编译器所做的工作。
例如运行jad.exe DateInterval.class,生成下面的反编译文件