一、什么是泛型

  本质而言,泛型指的是参数化的类型。参数化的类型的重要性是:它能让你创建类、接口和方法,由它们操作的数据类型被指定为一个参数。操作参数化类型的类、接口或方法被称为泛型,如泛型类或泛型方法。着重理解的是,通过对Object类型的引用,Java总是可以创建一般化的类、接口和方法,因为Object是所有其他类的超类,一个Object引用可以是任何类型的对象,因此,在泛型出现以前的代码,一般化的类、接口和方法是使用Object引用来操作不同类型的对象(可不可以说是多态?),这样做的问题是不能保证类型安全。泛型添加了它们正缺乏的类型安全,同时还简化了过程,因为不需要显示地在Object和实际操作的数据类型之间进行强制转换。利用泛型,所有的强制转换都是自动和隐含的,因此,泛型扩展了代码复用的能力,而且既安全又方便。

下面是一个简单的泛型例子:

  

 1 package com.hujianjie.demo;
 2 
 3 //一个简单的泛型类
 4 class Gen<T> {
 5     T ob;
 6 
 7     Gen(T o) {
 8         this.ob = o;
 9     }
10 
11     T getOb() {
12         return ob;
13     }
14 
15     void showType() {
16         System.out.println("Type of T is" + ob.getClass().getName());
17     }
18 }
19 
20 public class GenDemo {
21     public static void main(String args[]) {
22         // 定义一个Integer类型
23         Gen<Integer> iob;
24         iob = new Gen<Integer>(88);//此处的<Integer>不能省,因为定义的类型是Integer类型,所以new返回的引用也必须是Integer,否则就会发生编译错误,也即泛型安全
25         iob.showType();
26         System.out.println("value:" + iob.getOb());
27         // 定义一个String 类型
28         Gen<String> sob;
29         sob = new Gen<String>("Generics Test!");
30         sob.showType();
31         System.out.println("value:" + sob.getOb());
32     }
33 
34 }

首先注意,类型Integer被放在Gen后面的一对尖括号内,在本例中,Integer作为类型变元被传递给Gen的类型参数T,这就创建了Gen的一个版本,在该版本中,所有对T的引用都被转换为对Integer的引用,因此,对于这个声明,ob成为Integer类型,且getob()的返回类型也是Integer类型。String类型也是类似分析。特别主意Java编译器并不创建实际的不同Gen版本或者任何其他的泛型类,尽管将其想象成这样是有帮助理解的,但实际上并不是这样发生的。相反,编译器删除所有的泛型类型信息,并进行必要的强制转换,使代码在行为上就好像创建了Gen的一个特定版本一样,因此,程序中只实际存在一个Gen版本。删除泛型类型信息的过程称为擦拭(erasure)。

 

二、泛型的特点。

  1、泛型只使用对象。声明泛型类型的一个实例时,传递给类型参数类型变元必须是类类型,不能使用基本类型,如int或char。

  2、泛型类型的差异基于类型变元。理解泛型类型的关键在于:对某种特定版本的泛型类型的引用,与对同一种泛型类型的另一种版本的引用是类型不兼容的。例如:上面代码中的 iob=sob 是错误的,虽然iob和sob都属于Gen<T>类型,由于它们的类型参数不同,它们引用的也是不同的类型。这就是泛型添加的类型安全和错误预防的原理所在。

  3、泛型能在编译时捕捉类型不匹配错误,因此能够创建类型安全的代码。通过泛型,原来的运行时错误现在成了编译错误,这是一个重要的提高。

 

 

三、泛型的一般形式

  声明泛型的语法: class class-name<type-param-list>{ . . . }

  声明一个泛型引用的语法: class-name<type-arg-list> var-name = new class-name <type-arg-list>(cons-arg-list);

 

四、泛型的类型

  1、有界类型。前面简单的例子中,类型参数可以替换为任意的类类型,多数情况下这一点是好的,但是有时候需要对可以传递给类型参数的类型做出限制,于是出现了有界类型。在指定一个类型参数的时候,可以创建一个上界,声明所有的类型变元都必须从超类派生,实现方法是通过在指定类型参数时使用一个extends子句,如下所示:

  < T extends superclass >

这行代码规定T只能由superclass或它的子类来替换。因此,superclass 提供了一个包括本身在内的上限。

除了用类类型作为有界之外,还可以使用接口类型,实际上,可以将多个接口指定为界,而且,界可以包括一个类类型和多个接口,此时,必须首先指定类类型,当界包含一个接口类型时,只有实现这个接口的类型变元才是合法的,若指定的界包含一个类和一个或者多个接口,则应使用 & 运算符连接它们。例如:

  class Gen < T extends MyClass & MyInterface > { . . . }

其中, T由称为MyClass 的类和称为MyInterface的接口界定,因此,任何传递给T的类型变元,都必须是MyClass 的一个子类,且实现MyInterface。

 

  2、通配符变元。通配符变元用 “?”指定,它代表一个未知类型

 1 package com.hujianjie.demo;
 2 
 3 /*
 4  * 定义一个有界泛型类
 5  */
 6 class Stats<T extends Number>{
 7     T[] nums;
 8     Stats(T [] o){
 9         nums=o;
10     }
11     double average(){
12         double sum = 0.0;
13         for(int i =0;i<nums.length;i++){
14             sum+=nums[i].doubleValue();
15         }
16         return sum/nums.length;
17     }
18     /*
19      * 这里使用通配符变元,如果是Stats<T>,两个比较的对象类型必须一致,否则,
20      * 会出现泛型安全错误,所以使用通配符,在不同类型间进行比较
21      */
22     boolean sameAvg(Stats<?> ob){
23         if(this.average()==ob.average())
24             return true;
25         return false;
26     }
27 }
28 
29 public class WildCardDemo {
30 
31     /**
32      * @param args
33      */
34     public static void main(String[] args) {
35 
36         Integer inums[]={1,2,3,4,5};
37         Stats<Integer> iob = new Stats<Integer>(inums);
38         System.out.println("iob average is :"+iob.average());
39         Double dnums[]={1.1,2.2,3.3,4.4,5.5};
40         Stats<Double> dob = new Stats<Double>(dnums);
41         System.out.println("dob average is :"+dob.average());
42         Float fnums[]={1.0f,2.0f,3.0f,4.0f,5.0f};
43         Stats<Float> fob = new Stats<Float>(fnums);
44         System.out.println("dob average is :"+fob.average());
45         if(iob.sameAvg(dob))
46             System.out.println("iob and dob are the same!");
47         else
48             System.out.println("iob and dob are not the same!");
49         if(fob.sameAvg(iob))
50             System.out.println("iob and fob are the same!");
51         else
52             System.out.println("iob and fob are not the same!");
53     }
54 
55 }

 

  3、有界通配符。通配符变元的界定方式与类型参数大体相同,当创建一个操作与类层次的泛型时,有界通配符尤其重要。一个有界通配符可以为类型变元指定一个上界或一个下界。如:在坐标系里,二维、三维、四维坐标,如果要限定在一个三维坐标中,则可以使用如下方式:showXYZ(Coords< ? extends ThreeD> ob).

  4、泛型构造函数。构造函数也可以是泛型,即使它们的类也不是泛型。

  5、泛型接口。泛型接口的指定方法与泛型类相似。

 1 package com.hujianjie.demo;
 2 /*
 3  * 定义一个泛型上界接口,所传对象必须实现Comparable接口,可以比较大小
 4  */
 5 interface MinMax<T extends Comparable<T> >{
 6     T min();
 7     T max();
 8 }
 9 /*
10  * 定义实现接口类时注意:MyClass声明参数类型 T 后要将它传递给MinMax,因为MinMax
11  * 需要一个实现Comparable的类型,实现类必须指定同样的界
12  * 此界一旦建立,就无需在implements字句中再次指定。
13  * class MyClass <T extends Comparable<T> > implements MinMax< T extends Comparable<T> >
14  * 这样指定是错误的
15  */
16 class MyClass <T extends Comparable<T> > implements MinMax<T>{
17     T [] array;
18     MyClass (T []o){
19         array=o;
20     }
21     public T min(){
22         T minValue =array[0];
23         for(int i=0;i<array.length;i++){
24             if(minValue.compareTo(array[i])>0)
25                 minValue=array[i];
26         }
27         return minValue;
28     }
29     public T max(){
30         T maxValue = array[0];
31         for(int i=0;i<array.length;i++){
32             if(maxValue.compareTo(array[i])<0){
33                 maxValue=array[i];
34             }
35         }
36         
37         return maxValue;
38         
39     }
40 }
41 
42 public class GenIFDemo {
43 
44     /**
45      * @param args
46      */
47     public static void main(String[] args) {
48         Integer inums[]={3,6,2,8,7};
49         Character chs[]={'b','r','c','d'};
50         MyClass<Integer> iob = new MyClass<Integer>(inums);
51         MyClass<Character> cob = new MyClass<Character>(chs);
52         System.out.println("Max values in inums is "+iob.max());
53         System.out.println("Min values in inums is "+iob.min());
54         System.out.println("Max values in chs is "+cob.max());
55         System.out.println("Min values in chs is "+cob.min());
56     }
57 
58 }

注意:如果一个类实现了一个泛型接口,则此类必须也是泛型,或者至少它接受传递给接口的类型参数。如果MyClass是一个没有引用泛型的类,只是实现了泛型接口如下声明,编译器会报错。

1 class MyClass implements MinMax< T >{ . . . }

另外,如果上面的情况稍作改变就不同,如果类实现一个泛型接口的特定类型,则此实现类不需要是泛型的

1 class MyClass implements MinMax< Integer>{ . . . }

  6、泛型类层次。和非泛型类一样,泛型类也可以是类层次中的一部分,因此,泛型类可以作为超类或子类。泛型类层次与非泛型类层次的关键区别在于:泛型类层次中,全部子类必须将泛型超类需要的类型变元沿层次向上传递。这与构造函数变元必须沿层次向上传递的方式类似。

 1 package com.hujianjie.demo;
 2 class Gen <T>{
 3     T ob;
 4     Gen (T o){
 5         ob =o;
 6     }
 7     T getOb(){
 8         return ob;
 9     }
10 }
11 class Gen2<T> extends Gen<T>{
12     Gen2(T o){
13         super(o);
14     }
15 }
16 
17 public class HierDemo {
18 
19     /**
20      * @param args
21      */
22     public static void main(String[] args) {
23         Gen<Integer> iob = new Gen<Integer>(88);
24         Gen2<Integer> iob2 = new Gen2<Integer>(99);
25         Gen2<String> str2 = new Gen2<String>("Generics Test");
26         if(iob instanceof Gen<?>)
27             System.out.println("iob is instance of Gen");
28         //iob 向下转型,将不是Gen2对象的类型
29         if(iob instanceof Gen2<?>)
30             System.out.println("iob is instance of Gen2");
31         if(iob2 instanceof Gen<?>)
32             System.out.println("iob2 is instance of Gen");
33         if(iob2 instanceof Gen2<?>)
34             System.out.println("iob2 is instance of Gen2");
35         if(str2 instanceof Gen<?>)
36             System.out.println("str2 is instance of Gen");
37         if(str2 instanceof Gen2<?>)
38             System.out.println("str2 is instance of Gen2");
39         /*
40          * 以下这些行不能被编译的原因是:它们试图比较iob2与Gen2的一个特定
41          * 类型,本例中是Gen2<Integer>.记住,在运行时无法得到泛型类型信息,因此
42          * 对instanceof来说,无法判断iob2是否为Gen2<Integer>的一个实例
43          */
44     //    if(iob2 instanceof Gen2<Integer>)
45     //        System.out.println("str2 is instance of Gen2");
46         
47     }
48 
49 }

 结果:

1 iob is instance of Gen
2 iob2 is instance of Gen
3 iob2 is instance of Gen2
4 str2 is instance of Gen
5 str2 is instance of Gen2

 

 

五、泛型擦拭

  大体而言,擦拭的工作方式是这样的:当编译Java代码时,全部泛型类型信息被移去(擦拭)。这意味着使用它们的界定类型来替换类型参数,如果没有显式地指定界,则界定类型是Object,然后运用适当得强制转换(由类型变元决定),以维持与类型变元指定的类型的兼容。编译器也会强制这种类型兼容。对泛型来说,这种方法意味着在运行时不存在类型参数,它们仅是一种源代码机制。在编译时所有的类型参数都被擦拭掉了,在运行时,只有原始类型实际存在。