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会去调用基类的方法,当然,这里运行时是会出错的,因为无法将字符串转为整型。这里目的就是保持泛型继承的多态性。