泛型编程(三)

泛型编程(三)——泛型代码与虚拟机

Java虚拟机没有所谓的泛型类型——所有的类型都是原始类型。当你定义泛型类时,编译器生成一个同名的原始类型,直接移除泛型参数,使用泛型参数的地方将会被他们的边界类型代替(如果没有的话,就使用Object类型代替)。
比如,之前定义的Pair<T>类型将会使用下面的类定义代替

public class Pair
{
	public Pair(Object first,Object second)
	{
		this.first = first;
		this.second = second;
	}
	public Object getFirst(){return this.first;}
	public Object getSecond() { return this.second; }

	public void setFirst(Object newValue) { this.first = newValue; }
	public void setSecond(Object newValue) { this.second = newValue; }

	private Object first;
	private Object second;
}

Pari类中的T是一个没有边界的变量,因此直接使用Object代替。编译器处理的结果就是一个常规类,就跟你学泛型类型之前定义类一样。
你的程序可能会使用多种类型的Pair类,但是经过转化之后,他们都使用同一个Pair类。这一点和C++不一样,在C++中,使用模板类实现泛型,模板类会为每个使用到的类型创建新的类。
原始类中替换的类型是第一个边界,如果没有边界,那就是 Object类型,比如上面的例子中,没有指定T的边界,因此直接使用Object替换。比如我们定义了另外一个类:

public class Interval<T extends Comparable & Serializable> implements Serializable
{
	public Interval(T first,T second)
	{
		if(first.compareTo(second) <= 0) { lower = first; upper = second; }
		else { lower = second; upper = first; }
	}
	...
	private T lower;
	private T upper;
}

那么Interval的原始类型看起来是这个样子的。

public class Interval implements Serializable
{
	public Interval(Comparable first,Comparable second){...}
	...
	private Comparable lower;
	private Comparable upper;
}

你可能会问,如果我换一种表达,会怎么变化呢,比如,如果我把定义换成class Interval<T extends Serializable & Comparable>会怎么样,答案是边以及会将T替换成Serializable,这是由编译器决定的,因此你应该将资源消耗更小的接口放在前面。

如何解释泛型语句

当你的程序调用泛型方法,编译器会在适当的地方加上类型转换。比如,考虑我们调用一个泛型类的方法

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

类型擦除机制会将getFirst的返回值变为Object类型。边以及自动添加语句将Object类型转换为Employee类型。当你获取泛型类的成员变量时,类似的语句也会被添加。比如,如果你获取Pair类中的first参数(不好的习惯,但是合法)

Employee buddy = buddies.getFirst();

类似的类型转换语句同样会被添加。

如何解释泛型方法

类型擦除也会发生在泛型方法中,方形方法的定义方法类似于

public static <T extends Comparable> T min(T[] a)

经过类型擦除后,方法定义为

public static Comparable min(Comparable[] a)

泛型范数T被边界类型Comparable替代。
方法的类型擦除会带来一些问题。考虑下面的问题

class DateInterval extends Pair<Date>
{
	public void setSecond(Date second)
	{
		if(second.compareTo(geteFirst()) >= 0)
		{
			super.getSecond(second);
		}
	}
	...
}

DateInterval继承自Pair<Date>类,我们想保证第二个更大。经过类型擦除后,

class DateInterval extends Pair
{
	public void setSecond(Date second){...}
}

但问题在于,还有一个集成自Pair的setSecond类。

public void setSecond(Object second)

很显然他们是不同的类,但他们本不应该不同。考虑下面的语句

DateInterval interval = new DateInterval(...);
Pair<Date> pair = interval; // 不应该出现问题,赋值为子类对象
pair.setSecond(aDate);

我们的期望是,setSecond方法应该遵循多态的规则,调用合适的方法。应为它指向的是DateInterval对象,因此应该调用DateInterval的setSecond方法。为了解决这个问题,编译器会在DateInterval类中生成一个过渡方法,

public void setSecond(Object second) { setSecond((Date) second); }

为了解释这个语句的作用,让我们仔细地讲一下下面这条语句的执行流程。

pair.setSecond(aDate);

变量pari声明为Pair<Date>类型,他有一个函数setSecond(Object)。虚拟机执行他指向的对象的对应函数。他只想的对象是DateInterval类型的,那么编译器执行DateInterval.setSecond(Object)函数,这个函数就是上面合成的过渡函数,在内部,他执行了DateInterval.setSecond(Date),也就是我们想要的函数。
过渡函数可能会更奇怪,假设DateInterval方法重写了getSecond()方法。

class DateInterval extends Pair<Date>
{
	public Date getSecond() { return (Date) super.getSecond().clone(); }
}

在类型擦除后,有两个getSecond()方法

Date getSecond() // 在DateInterval中定义
Object getSecond() // 在Pair中定义。

在java中,你没有办法编写这两个函数,java不允许两个参数类型相同的同名函数。但是,在java虚拟机中,两个同名函数有相同的参数,但返回值不同是可以的。因此,编译器可以根据返回值不同,生成不同的字节码,正确运行程序。
总之,在java中,如果你编写泛型类,需要注意:

  • 虚拟机中没有泛型类,只有原始类型和方法
  • 所有类型变量被边界类型代替。
  • 为了防止类继承时的多态问题,会生成过渡方法。
  • 会自动插入类型转换语句。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值