Java泛型的实现原理
泛型,就是一个参数化了的类或接口。裸类型(raw type)就是指去掉了类型参数信息的类型。Java 为了保持兼容性,泛型的实现并不像在C#中那么彻底,看下面一个具体的泛型类,
public class Node<T, U extends Number> {
private T data;
private List<U> list;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public void setData2(List<U> l) {
list = l;
}
public U getFirstData(){
return list.get(0);
}
public T getData() { return data; }
// ...
}
在编译这个泛型的时候,编译器会做一个叫做去泛型类型(Erasure of Generic Types)的处理,具体的处理内容如下:
[1] 泛型类型参数被直接去掉,并把所有的类型参数进行替换。对有Bounded的参数,则使用相应的边界类型,例如,如果泛型参数是<U extends Number>,那么这个参数会被直接转换为Number。如果是类型是像我们上面的这种<T>,那么<T>会被装换为Object。
[2] 进行必要的类型装换,以保证类型安全。
[3] 生成桥接方法保证集成泛型类型时,多态特性仍然工作正常。
去泛型类型(Erasure of Generic Types)和相关的强制类型转换
这个主要和[1]、[2] 两条相关,根据第[1]条,Java编译器会把这个泛型类编译为如下:
//类型参数被直接去掉
public class Node {
//类型参数T被替换为Object
private Object data;
//U被替换为Number
private List data2;
//类型参数被直接去掉
private Node next;
//类型参数被直接去掉,类型参数T被替换为Object
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
//U被替换为Number
public void setData2(List l) { list = l; }
//U被替换为Number, 经过必要的类型转换后,实际会变为 public Number getData2() { return (Number)list.get(0); }
public Number getData2() { return list.get(0); }
//类型参数T被替换为Object
public Object getData() { return data; }
// ...
}
经过这个处理后,我们看看第23行经过处理后的代码,这里其实是有问题的。因为list.get(0)返回的是一个Object对象,而getData2方法的返回值是一个Number类型,Object类型不能直接赋值给Number类型,所以这里必须做一个强制装换。这也是我们上面说到的第[2] 条的意义所在,经过第[2] 条的规则,第23行实际上会被编译为:
public Number getData2() { return (Number)list.get(0); }
这样,不管我们在使用泛型
的时候使用什么具体的类型,上面的代码都是能够保证类型安全的。例如,
Node<String, Integer> node1 = new Node<String, Integer>(); Node<List, Short> node2 = new Node<List, Short>();
在使用的时候,Node<String, Integer>和Node<List, Short>都被转为了Node进行使用,并没有Node<String, Integer>和Node<List, Short>这两种类型的存在。编译器会进行类型转换以保证在调用相关方法时的类型安全,这需要做强制类型转换,比如我们写如下的代码:
Node<String, Integer> node1 = new Node<String, Integer>(); Integer somenumber = node1.getData2()
这段代码会被编译为类似如下,因为getData2返回的是一个Number类型,这里必须做一个强制转换:
Node<String, Integer> node1 = new Node<String, Integer>(); Integer somenumber = (Integer)node1.getData2();
桥接方法(Bridge Methods)
先看一个例子如下(例子照搬Oracle的相关文档):
public class Node<T> {
private T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
更具我们前面已经讲到的,这段代码在经过去泛型类型(Erasure of Generic Types)后,会变为如下的样子:
public class Node {
private Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println(Integer data);
super.setData(data);
}
}
注意看这里,经过去泛型类型(Erasure of Generic Types)后,考虑如下的代码:
MyNode mn = new MyNode(5);
Node n = (MyNode)mn;
n.setData("Hello");
mn.data //??
这里特别注意第3行,因为基类和子类分别有一个setData(Object)和setData(Integer)方法,由于方法的参数不同,这两个实际上是重载方法,所以第三行回去调用Node的setData(Object)。所以,这个时候data到底是个什么值?明显这里是有问题的,继承的多态性没有保存下来。这里就需要做我们之前提到的第[3]条了,编译器会给子类添加一个setData(Object)方法,这个方法称为桥接方法如下:
class MyNode extends Node {
//编译器自动生成的桥接方法
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
// ...
}
现在再看下面的代码:
MyNode mn = new MyNode(5);
Node n = (MyNode)mn;
n.setData("Hello");
这里的setData会去调用基类的方法,当然,这里运行时是会出错的,因为无法将字符串转为整型。这里目的就是保持泛型继承的多态性。

浙公网安备 33010602011771号