java和其他语言一样,都支持泛型,包括泛型类和泛型方法,但是java的泛型比较特殊。因为java的泛型并不是在java诞生之初就加入的,在很长的一段时间里,java是没有泛型的,在需要泛型的地方,统统都采用协变的方式,也就是采用Object,比如ArrayList类,元素的类型就是Object。为了兼容原来的代码,java的设计者希望在加入泛型之后,所有的泛型都可以传给原来的对应的非泛型参数,例如,可以把ArrayList<xxx>传给原来接收ArrayList参数的方法。为了达到这个目的,java的设计者采取了一种叫做“类型擦除”的实现方式,把泛型拦截在了编译阶段,在虚拟机中,所有的泛型参数最终的类型都会是转换后的Object类型(如果有类型限定,则是限定后的类型,如 <T extends Super>,则泛型T在虚拟机中统一都是Super类型),然后利用协变性而兼容所有类型或限定类型参数的传入,从而实现泛型。

比如用户定义泛型类:

 



class Super<T,V>{
     T a;
     public V mehod(T arv){
         a=arv;
        return a;
     }
 }



 

在经过编译阶段的类型擦除之后,虚拟机看到的Super类是这样的:



class Super{
     Object a;
     public Object mehod(Object arv){
         a=arv;
        return a;
     }
 }



 

而且可以看到,无论有多少个实例化后的泛型,比如Super<String,String>、Super<Interger,Double>,在虚拟机中,都只存在一个Super类。

在应用泛型类的时候,如果返回值也是泛型,那么返回的将是Object,但是很显然,我们在使用泛型的过程中,并没有要显示的进行类型转换,比如我们不需要这样 String a=(String) super.method("hahaha"); 而是直接 String a= super.method("hahaha");

则是因为,为了返回实现类型的自动匹配,java编译器会在“类型擦除”的基础上在进行“转换插入”操作。每当使用泛型的返回值时,编译器会制动插入类型转换,所以原始代码是 String a= super.method("hahaha"); ,编译之后,虚拟机看到的却是 String a= (String)super.method("hahaha");

 

因为类型擦除后,原有的泛型方法的泛型参数都被替换成了Object,因此无法实现方法覆盖,但实际应用中,肯定需要实现方法覆盖从而达到多态的目的。

比如有用户继承了Super<T,V>这个泛型,实现了子类SubClass:Super<String,String>:



class SubClass:Super<String,String>{
     String a;
     public String mehod(String arv){
         a=arv;
        return a;
     }
 }



因为用户只实现了方法 public ,而父类的method方法是 public



public Object mehoed(Object arv){
    return method((String) arv);
}



桥接方法的方法签名和返回值和父类的泛型方法 public 一致,因此可以覆盖父类的method方法,实现了多态。通过桥接方法,还实现了对用户定义的method方法的调用,在程序员看来,就好像 public 成功覆盖了 public

 

正因为java是以这种比较奇怪的方式实现了泛型,因此一切new T(..)或者new T[]其实都会是new Object,没有任何意义,所以java干脆在编译阶段就禁止了这种语法,也就是说,泛型只能传入,不能产生。

除了不能产生泛型外,其它场合泛型和具体类型无差别,泛型参数可以用于类型转换、方法参数的类型等各种场合,比如这样 (T)a;

但是需要注意的是,很多看起来像产生泛型的语法,其实并不是真正产生泛型,比如 List<String>=new ,这里产生的只有一个ArrayList,并且这个ArrayList的类型参数被传入了一个String,这个String将用于ArrayList的“桥接方法”、“转换插入”等用处。实际上,在ArrayList<>的内部,是以一个Object[ ]来存放数据的,而不是String[ ],在你add("hahaha")的时候,这个泛型还是被传入的。在get(0)时,这个被传入的类型String在“转换插入”中发挥了作用,每次调用 get(0); 都被转换成了 (String)get(0);

上面说泛型在编译阶段就被“擦除”了,其实也不完全对,利用反射,仍然可以获取泛型信息,比如泛型参数、泛型限制、通配符信息等都可以获取到,说明在java的字节码(即.class文件)中,泛型信息仍然存在。因此,更准确的说,是泛型经过类加载器加载到虚拟机之后,泛型比擦除了,但是泛型的信息仍然存在字节码中,并且可以通过反射获取到。