一、什么是泛型
本质而言,泛型指的是参数化的类型。参数化的类型的重要性是:它能让你创建类、接口和方法,由它们操作的数据类型被指定为一个参数。操作参数化类型的类、接口或方法被称为泛型,如泛型类或泛型方法。着重理解的是,通过对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,然后运用适当得强制转换(由类型变元决定),以维持与类型变元指定的类型的兼容。编译器也会强制这种类型兼容。对泛型来说,这种方法意味着在运行时不存在类型参数,它们仅是一种源代码机制。在编译时所有的类型参数都被擦拭掉了,在运行时,只有原始类型实际存在。