Java-泛型编程-类型擦除(Type Erasure)

Java中的泛型代码和C++中的模板有一个很大的不同:C++中模板的实例化会为每一种类型都产生一套不同的代码,这就是所谓的代码膨胀。
Java中并不会产生这个问题。虚拟机中并没有泛型类型对象,所有的对象都是普通类。

虚拟机中的泛型转换需要记住4条事实:
1) 定义任何一个泛型都会自动产生其原始类型(raw type)
2) 这个过程中,泛型类型会被擦除,替换为其限定类型(bounding type)
3) 必要时插入强制转换来保证类型安全
4) 使用桥接方法(bridge method)来保证正确处理多态


例如写一个返回数组中最小值的泛型函数,可以如下定义。
public static <T extends Comparable> T min(T[] a)

(实际上Comparable也是一个泛型接口,所以最正确的定义方法是public static <T extends Comparable<? super T>> T min(T[] a)  )


类型擦除后,替换为限定类型Comparable,其原始类型就是
public static Comparable min(Comparable[] a)

如果没有限定类型,那么类型擦除后就替换为Object。例如一个Pair<T>类
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Pair<T> {  
  2.     private T first;  
  3.     private T second;  
  4.   
  5.     public Pair(T first, T second) {  
  6.         this.first = first;  
  7.         this.second = second;  
  8.     }  
  9.   
  10.     public T getFirst() {  
  11.         return first;  
  12.     }  
  13.   
  14.     public T getSecond() {  
  15.         return second;  
  16.     }  
  17.   
  18.     public void setFirst(T newValue) {  
  19.         first = newValue;  
  20.     }  
  21.   
  22.     public void setSecond(T newValue) {  
  23.         second = newValue;  
  24.     }  
  25. }  
 
Pair<T>类执行类型擦除后的原始类型就是 
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class Pair {  
  2.     private Object first;  
  3.     private Object second;  
  4.   
  5.     public Pair(Object first, Object second) {  
  6.         this.first = first;  
  7.         this.second = second;  
  8.     }  
  9.   
  10.     public Object getFirst() {  
  11.         return first;  
  12.     }  
  13.   
  14.     public Object getSecond() {  
  15.         return second;  
  16.     }  
  17.   
  18.     public void setFirst(Object newValue) {  
  19.         first = newValue;  
  20.     }  
  21.   
  22.     public void setSecond(Object newValue) {  
  23.         second = newValue;  
  24.     }  
  25. }  

下面一段代码,
        Pair<String> pair = new Pair<String>("1", "2");
        String s = pair.getFirst();
类型擦除后,getFirst()返回Object。所以,编译器实际上把它转换为下面的虚拟机指令:
a) 调用原始类型的Pair.getFirst
b) 把返回的Object对象强制转换为String


类型擦除有时候会带来很复杂的情况:
例如下面写了一个新的类:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. class DateInterval extends Pair<Date> {  
  2.     public DateInterval(Date first, Date second) {  
  3.         super(first, second);  
  4.     }  
  5.       
  6.     public void setSecond(Date second) {  
  7.         if (second.compareTo(getFirst()) >= 0) {  
  8.             super.setSecond(second);  
  9.         } else {  
  10.             throw new IllegalArgumentException("Second date should be no earlier than first date.");  
  11.         }  
  12.     }  
  13.       
  14.     public Date getSecond() {  
  15.         return (Date) super.getSecond().clone();  
  16.     }  
  17. }  

类型擦除后,DateInterval中就有两个setSecond方法。
public void setSecond(Object second)
public void setSecond(Date second)
在这里,类型擦除和多态就产生了冲突。因为实际上我们是想要覆盖基类的方法。
为了解决这类问题,编译器会产生一个桥接方法
public void setSecond(Object second) {
    setSecond((Date) second);
}

类型擦除后,DateInterval中有两个getSecond方法
public Date getSecond()
public Object getSecond()
在Java代码中,直接这么申明两个方法,是会导致编译错误的,因为不允许两个同名方法具有相同的参数类型。
但是,在虚拟机中,也就是编译后的代码中,参数类型加上返回类型来识别一个方法,所以这样做是允许的,因为返回类型不一样。
实际上,编译器生成这样一个桥接方法。
public Object getSecond() {
return getSecond();
}

可以使用Jad工具来反编译class文件,就能清除到看到编译器所做的工作。
例如运行jad.exe DateInterval.class,生成下面的反编译文件

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.  
  2. // Jad home page: http://www.kpdus.com/jad.html  
  3. // Decompiler options: packimports(3)   
  4. // Source File Name:   Pair.java  
  5.   
  6. package learning.generic;  
  7.   
  8. import java.util.Date;  
  9.   
  10. // Referenced classes of package learning.generic:  
  11. //            Pair  
  12.   
  13. class DateInterval extends Pair  
  14. {  
  15.   
  16.     public DateInterval(Date first, Date second)  
  17.     {  
  18.         super(first, second);  
  19.     }  
  20.   
  21.     public void setSecond(Date second)  
  22.     {  
  23.         if(second.compareTo((Date)getFirst()) >= 0)  
  24.             super.setSecond(second);  
  25.         else  
  26.             throw new IllegalArgumentException("Second date should be no earlier than first date.");  
  27.     }  
  28.   
  29.     public Date getSecond()  
  30.     {  
  31.         return (Date)((Date)super.getSecond()).clone();  
  32.     }  
  33.   
  34.     public volatile void setSecond(Object obj)  
  35.     {  
  36.         setSecond((Date)obj);  
  37.     }  
  38.   
  39.     public volatile Object getSecond()  
  40.     {  
  41.         return getSecond();  
  42.     }  
  43. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值