泛型是JDK1.5的一项新增加的特性,它的本质是参数化类型(Parametersized Type)的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
Java中的泛型对应于C++中的模板,是一种抽象的编程方式,开发者定义类和方法的时候可以用一种通用的方式进行定义而不必写出具体的类,这些未知的东西会在真正使用的时候再确定。
先上一段代码:
public class TestTemplate {
public static void main(String[] args) {
//不使用泛型
List list1 = new ArrayList();
list1.add("hello");
list1.add("world");
Iterator it = list1.iterator();
while(it.hasNext()){
Object obj = it.next();
String val = (String)obj;
System.out.println(val);
}
//使用泛型
List<String> list2 = new ArrayList<String>();
list2.add("hello");
list2.add("world");
for(String str : list2)//使用foreach进行遍历,类型直接转换为String
System.out.println(str);
}
}
上面的代码中list1没有使用泛型,取元素时先是Object类型,在具体使用过程中再强制转换成对应的类型;list2直接在创建的时候就提供了元素的泛型,因此获取元素时类型也会自动转换,这也是集合使用泛型的好处。
但是泛型技术在C#和Java之中的使用方式看似相似,实则有着本质的区别,C#的泛型无论在程序源码中、编译后的IL(Intermediate Language)中,还是运行期的CLR中都是切实存在的,List<Integer>与List<String>就是两个不同的类型,它们在系统运行期生成、有自己的虚方法表和类型数据,这种方法实现的泛型称为真实泛型。
Java语言中的泛型规则不一样,它只在程序源码中存在,在编译后的字节码中就已替换为原来的原生类型(Raw Type,也称为裸类型),并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说ArrayLIst<int>与ArrayList<String>就是同一个类,所以泛型技术是Java语言的一颗语法糖,这种方法实现的泛型称为伪泛型。
所以上面的代码,list2编译成Class文件,然后再用字节码反编译工具反编译后,将会发现泛型不见了,程序又变回了Java泛型出现之前的写法。因此list2泛型擦出之后就是list1。
那么有了泛型擦出,再看如下程序:
public class TestTemplate {
public static void method(List<String> list){
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list){
System.out.println("invoke method(List<Integer> list)");
}
}
编译自然是通不过的,因为编译后类型擦出,两方法的特征签名就变得一模一样了,但方法就不可以重载了吗?在《深入理解Java虚拟机》一书中,作者给出了如下例子:
public class TestTemplate {
public static String method(List<String> list){
System.out.println("invoke method(List<String> list)");
return "OK";
}
public static int method(List<Integer> list){
System.out.println("invoke method(List<Integer> list)");
return 0;
}
public static void main(String[] args) {
method(new ArrayList<String>());
method(new ArrayList<Integer>());
}
}
并给出的解释是方法重载要求方法具备不同的特征签名,返回值并包含在方法的特征签名之中,所以返回值不参与重载选择。但是在Class文件格式中只要描述符不完全一致的两个方法就可以共存,两方法如果有相同的名称和签名,但返回值不同那它们也是可以合法地共存于一个Class文件中。
(但笔者在1.8.0_92的版本下编译执行通不过,所以此处还有一定疑惑)