细说java泛型(三)

泛型代码和虚拟机
虚拟机没有泛型类型对象,所有的对象都是属于普通类。

类型擦除

无论何时定义一个泛型类,都会自动提供一个相应的原始类型,这个原始类型的名字就是去掉类型参数后的泛型类型名,类型变量会被擦除,并替换成其限定类型,对于无限定的变量则替换为Object。
例如,Pair<T>的原始类型如下所示:

public class Pair{
	private Object first;
	private Object second;
	
	public Pair(Object first,Object  second){
		this.fiirst=first;
		this.second=second;
	}
	public Object getFirst(){return first;}
	public Object getSecond(){return second;}
	
	public void setFirst(Object newValue){first=newValue;}
	public void setSecond(Object newValue){second=newValue;}
} 

因为T是一个无限定的变量,所以直接用Object替换。
替换的结果就好像java语言中引入泛型之前实现的普通类一样。
在程序中可以包含不同类型的Pair,如Pair<String>或者是Pair<LocalDate>。不过擦除类型后,他们都会变成原始的Pair类型。

原始类型用第一个限定来替换类型变量,如果没有给定限定,就替换为Object。
假如有这么一串代码:

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

原始类型Interval如下所示:

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

注:如果限定切换为class Interval<T extends Serializable &> Comparable>的话,原始类型会用Serializable替换T,这验证了原始类型用第一个限定来替换类型变量这句话,但这里,编译器在必要的时候回向Comparable插入强制类型转换,因为使用了Comparable类的compareTo方法,需要转换了才能使用,而且在这里的Serializable接口是一个标签接口(即没有方法的接口),为了提高效率,应该将标签接口放在限定列表的末尾。

转换泛型表达式

编写一个泛型方法调用时,如果擦除了返回值类型,编译器会插入强制类型转换,例如:

Pair<Person> buddies = ...;
Person person = buddies.getFirst();

getFirst方法擦除类型后的返回值类型是Object,而这里返回的是一个Person类型,因此编译器会自动插入一个Person的强制类型转换,也就是说,编译器执行了两条虚拟指令:

  • 对原始方法Pair.getFirst的调用
  • 将返回的Object类型强制转换为Person类型

当访问一个泛型字段时也要插入强制类型转换,假如Pair类的first字段和second字段都是public的,如下表达式:

Person man = buddies.first;

也会在结果字节码中插入强制类型转换。

转换泛型方法

类想擦除也会出现在泛型方法中,如下:

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

这一类代码会被认为是整个一组方法,而擦除之后就剩下:

public static Comparable min(Comparable[] a)

注意:类型擦除T已经被擦除了,只会留下限定类型Compaable。
但是,方法的擦除带来了两个问题,上码:

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

日期区间是一对LocalDate对象,而且我们需要覆盖这个方法来确保第二个值永远不小于第一个值,这个类擦除后变成:

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

但是不要忘了,我们还有一个继承来的方法:

public void setSecond(Object second){...}

这两个明显不是同一个方法,因为参数的类型不一样,但是按理说,不应该不一样,考虑如下语句:

DateIntervalinterval = new DateInterval(...);
Pair<LocalDate> pair = interval;
pair.setSecond(aDate);

这里我们希望setSecond方法的调用具有多态性,会自动调用合适的方法,由于pair引用了一个DateInterval对象,所以应该调用DateInterval.setSecond方法,问题在于来写擦除与多台发生了冲突,为了解决这个问题,编译器在DateInterval类中生成了一个桥方法:

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

要理解这句代码,我们可以从这里入手:

pair.setSecond(aDate);

变量pair已经声明为类型Pair<LocalDate>,并且这个类型只有一个名为setSecond的方法,即setSecond(Object)。虚拟机在pair引用的对象上调用这个方法,这个对象是DateInterval类型,因而会调用DateInterval.setSecond(Object)方法,这个方法是合成的桥方法,她会调用DateInterval.setSecond(LocalDate)。

桥方法可能有些难理解,假设DateInterval类也覆盖了getSecond方法:

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

在DateInterval类中,有两个getSecond方法:

LocalDate getSecond();
Object getSecond();

其实这样是错误的,我们指导重载的定义里面有一条是不能有相同的参数类型,而这里两个都没有参数类型,视为有相同的参数类型。但是在虚拟机中,会有参数类型和返回类型共同指定一个方法。因此编译器可以为两个仅返回值类型不同的方法生成字节码,虚拟机能够正确的处理这个情况。

总之,对于java泛型的转换,总结如下:

  • 虚拟机中没有泛型,只有普通类和方法。
  • 所有的类型参数都会替换为他们的限定类型。
  • 会合成桥方法来保持多态。
  • 为保持类型的安全性,必要时会插入强制类型转换。

java泛型(三)总结到这,后续我会继续更新关于java泛型的知识,有疑问或者我哪里写错了可以留言请教或指导,欢迎。

我是飞扬,专注于写bug的程序猿

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值