java的设计采用了单根结构,除去在GC上的好处之外,在泛型编程上(模板C++),因为所有的类型都继承自Object,因此利用向上塑型,我们是可以写出如下的代码:
public class GenericTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); // exception
System.out.println("name:" + name);
}
}
}
可以看到,不需要显示的模板声明,我们是可以把string和int都放入List里,但是问题是在向下塑型的过程中,如果把int转成string是不行的,java使用Exception来处理此种情况。
Java SE5之后也引入了参数化类型的概念,也就是泛型:
List<String> list = new ArrayList<String>();
list.add("qqyumidi");
list.add("corn");
//list.add(100); // 提示编译错误
这就是二者都遵循的泛型,但是java的泛型实现与c++是不同的:
public class GenericTest {
public static void main(String[] args) {
Box<String> name = new Box<String>("corn");
Box<Integer> age = new Box<Integer>(712);
System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box
System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box
System.out.println(name.getClass() == age.getClass()); // true
}
}
可以看到,在运行时,不同的泛型实际上是用一个类型,同样意义的代码在c++里完全是两回事情:
在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除(crased),并使用其限定类型(无限定的变量用Object)替换。
Java代码
1. class Pair<T> {
2. private T value;
3. public T getValue() {
4. return value;
5. }
6. public void setValue(T value) {
7. this.value = value;
8. }
9. }
Pair<T>的原始类型为:
Java代码
1. class Pair {
2. private Object value;
3. public Object getValue() {
4. return value;
5. }
6. public void setValue(Object value) {
7. this.value = value;
8. }
9. }
编译器如何处理泛型?
通常情况下,一个编译器处理泛型有两种方式:
1.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)实现的。