眼观千遍 → 耳听万遍 → 不如手动一遍√

  :: :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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

posted on 2012-11-14 21:04  无恙  阅读(1670)  评论(0编辑  收藏  举报