Java学习笔记-泛型的擦除

1.理解泛型类型的擦除

大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
如在代码中定义List和List等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别。
我们观察如下代码:

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);
        System.out.println(list1.getClass() == list2.getClass());
    }
}

在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList泛型类型的,只能存储字符串;一个是ArrayList泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。
我们还可以通过反射进一步理解擦除的过程:

public class scratchTest{
	public static void main(String[] args)throws Exception{
		ArrayList<Integer> list=new ArrayList<Integer>();
		list.add(1);
		list.getClass().getMethod("add",Object.class).invoke(list,"asd");//Method对象的invoke方法来委托动态构造的对应类型对象,使其执行对应形参的add方法,这是回调函数(方法)的功能
		for(int i=0;i<list.size();i++){
			System.out.println(list.get(i));
		}
	}
}

在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。

2.类型擦除后保留的原始类型

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定类的用Object进行替换)。
例如:Pair所示:

package generic;

public class Pair<T>{
	private T first;
	private T second;
	public Pair(){
		first=null;
		second=null;
	}
	public Pair(T first,T second){
		this.first=first;
		this.second=second;
	}
	public T getFirst(){
		return first;
	}
	public T getSecond(){
		return second;
	}
	public void setFirst(T newValue){
		first=newValue;
	}
	public void setSecond(T newValue){
		second=newValue;
	}
}

而Pair的原始类型如下所示:

public class Pair{
	private Object first ;
	private Object  second;
	public Pair(){
		first=null;
		second=null;
	}
	public Pair(Object first,Object  second){
		this.first=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 进行替换。
在程序中可以包含不同类型的Pair,例如Pair< String >等。而擦除类型后就变成原始类型了。
如果类型变量有限定,那么原始类型就用第一个边界的类型变量类替换。
比如: Pair这样声明的话

public class Pair<T extends Comparable> {}

那么原始类型就是Comparable。

3.调用泛型方法

首先,我们需要明确,当程序调用泛型方法的时候,如果擦除返回类型,编译器插入强制类型转换。
类型擦除也会出现在泛型方法中,程序员通常认为下述的泛型方法

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(getFirst())>=0)
			super.setSecond(second);
	}
}

一个日期区间是一对Date对象,并且需要覆盖这个方法来确保第二个值不会小于第一个值。这个类擦除之后变成:

class DateInterval extends Pair{
	public void setSecond(Date second){//注意这是DateInterval擦除后的方法,即DateInterval的方法!
	...
	}
}

但是我们看出,还是存在着另一个从Pair继承下来的方法

public void setSecond(Object second)//注意这是Pair< Date >擦除后的方法,即Pair方法!

这并不是同一种方法呀!
考虑下面的语句序列:

DateInterval interval=new DateInterval(...);
Pair<Date> pair=interval;
pair.setSecond(Date);

在这里,希望对setSecond的调用具有多态性,并调用最合适的那个方法。由于pair引用DateInterval对象(Pair< Date > pair=interval;),所以应该调用DateInterval.getSecond。
为解决这个问题,我们需要编译器在DateInterval类中生成一个桥方法

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

要想了解它的工作过程,请仔细跟踪下列语句的执行:

pair.setSecond(Date);

pair声明为类型Pair< Date >,并且这个类型只有一个简单的方法叫setSecond,即setSecond< Object >,虚拟机用pair引用的对象调用这个方法。这个对象是DateInterval类型的,因而将会使用DateInterval.setSecond(Object)方法。这个方法就是一个合成的桥方法。它调用DateInterval.setSecond(Date),这正是我们所期望的操作效果。
总之,需要记住有关Java泛型转换的事实:
1.虚拟机中没有泛型,只有普通的类和方法。
2.所有的类型参数都用它们的限定类型替换。
3.桥方法被合成来保持多态。
4.为保持类型安全性,必要时插入强制类型转换。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值