注:代号Tiger,内部版本为“Java 2,Standard Edition , 5,version 1.5”,以下我们为了简称为“Java5.0”
Java5.0引入了一个很重要概念就是Generic(泛型),简单的说泛型的引入,可以使们更安全的使用集合类。我们知道集合(List,Set,Map)中可以存储任何类型的数据,而我们在编程中要操作一个集合的话,就必须要写大量的代码逻辑对接收到的集合中的数据进行类型判断,否则就会造成ClassCastException异常,代码的健壮性很差。而且也不能很好的表示我们的方法接收集合中的类型到底工是什么?而Generic(泛型)的引入可以使们更细粒度的对集合中元素的类型进行限定,最大程度加强程序的健壮性。大家是不是已经有点期待了,闲话少说,我们进入正题......
例:我们现有一个需求就是将用户表中的用户名存储在List中并返回给前台进行展示Java1.4以前:List names = new ArrayList() ; if( names != null ) { for( int i = 0 ; i < names.length ; i++ ) { String name = ( String ) names.get( i ) ; System.out.println( "name:" + name ) ; }}Java1.5版本:List<String> names = new ArrayList<String>() ;if( names != null ) { for( int i = 0 ; i < names.length ; i++ ) { String name = names.get( i ) ; System.out.println( "name:" + name ) ; }}
大家看一下这两种写法: Java1.4以前的版本:我们无法对List的元素进行类型限定,所以当我们前台接受到这样一个集合后,就必须在遍历时对每条记录进行类型判断,否则就很有可能会出现异常(尤其我们在实现大型开发过程中,我们所拿到的数据并不一定是我们自己组织的,所以我们并不能确保集合中数据类型的一致性)。在遍历集合中的元素时,Java1.4及以前的版本,即使我们知道其中的所有元素类型都是String的,我们也必须要手动的进行强制类型,否则是无法通过编译的。一言以蔽之,通过泛型,原来的运行时错误现在成了编译时错误。 Java1.5版本: 的List集合,对其中的元素类型做了明确的要求,如果我们想把一个其它类型的数据放入到这个集合中,这是根本不被允许,无法编译通过。所有有类型转换是隐式的,不需要我们的参与,减轻开发人员的工作量,增强的使用集合类的安全性。如果有类型不匹配的数据,编译时就无法通过,而不等到运行时才发生异常。尤其大型开发中,这种风格代码更加健壮、易使用及易阅读。
在泛型出现之前,通用类、接口和方法是使用Object引用来操作不同类型的对象,这样做的问题是不能保证类型安全。泛型弥补了所缺乏的类型安全,也简化了过程,不必显式地在Object与实际操作的数据类型之间进行强制类型转换。通过泛型,所有的强制类型转换都是自动和隐式的,因此泛型扩展了重复使用代码的能力,而且既安全又简单。
=========================Writed by Jack.Hao on 2011.01.20========================
一、泛型的简单例子
class Gen<T> { T ob ; Gen( T o ){ ob = o ; } T getob() { return ob ; } public static void main( String [] args ) { Gen<Integer> iob ; iob = new Gen<Integer>( 88 ) ; int v = iob.getob() ; Gen<String> strOb = new Gen<String>( "Test" ) ; String str = strOb.getob() ; }}
class Gen<T>{}这里的T是类型参数的名称,实际上是一个占位符,会被具体的数据类型所替代。注意每当声明类型参数时,就得将它括在尖括号中。因为Gen使用一个类型参数,所以Gen是一个泛型类,也被称为参数化类型。 Gen<Integer> iob ; 类型Integer被括在Gen后面的尖括号内。Integer作为类型实参传递给Gen类型形参(T),这就创建了一个Gen的版本,在该版本中,所有对T的引用都被转换为对Integer的引用。因此,由于此声明,iob成为Integer类型,且getob()的返回类型也是Integer。 Gen<String> strOb ;仿佛也是创建了一个String类型的Gen版本。
注:实际上,Java编译器并不创建实际的不同Gen版本和任何其他的泛型类,相反,编译器会删除掉所有的泛型信息,并进行必须强制类型转换,但在代码上仿佛是创建特定的Gen版本。因此,程序中实际只存在一个Gen版本。删除泛型信息的过程称为擦拭。
Gen<Integer> iob ;iob = new Gen<Integer>( 88 ) ; //OK 等价于: iob = new Gen<Integer>( new Integer( 88 ) ) ;iob = new Gen<Double>( 88.0 ) ; //Error 同一泛型的不同类型版本之间是互不兼容的Test<int> t1 = new Test<int>(88); //Error 声明一个泛型实例时,传递给形参的必须是类类型,不可以是基本数据类型iob = strOb ; //Error 由于他们的参数类型不同,他们的引用也是不同的类型
泛型自动保证了汲及Gen的所有类型操作的安全,在这个过程中不必输入人工强制类型转换和类型检验的代码。
=========================Writed by Jack.Hao on 2011.01.20========================
二、多个参数的泛型类
class TwoGen<T,V> { T ob1; V ob2; TwoGen( T o1 , V o2 ){ ob1 = o1 ; ob2 = o2 ; }}
它指定了两个参数:T和V,中间用逗号分隔。因为它具有两个类型参数,当创建一个对象时,必须给TwoGen传递两个类型参数:TwoGen<Integer,String> obj = new TwoGen<Integer,String>(88,"test"); //多个参数类型之间使用逗号分隔
=========================Writed by Jack.Hao on 2011.01.20========================
三、泛型类的语法
class class-name<type-param-list>{......}
引用声明泛型类的语法:
class class-name<type-arg-list> var-name =
new class-name<type-arg-list>( cons-arg-list ) ;
=========================Writed by Jack.Hao on 2011.01.20========================
四、有界类型
有时我们需要对可以传递给类型参数的类型做出限制。例如:你创建一个泛型类,用于计算一组数(int,float,double)的平均值,因此要求这个类型参数必须是一个数字类型,该如何做出限制呢?
实现方法是通过在指定类型参数时使用一个extends关键字,表示所有的类型变量必须从指定的超类进行派生(指定类的直接或间接子类)。如:<T extends superclass> 代码中的T只能由superclass或superclass的子类来替换。因此,superclass提供了一个包括本身在内的上限 class Stats<T extends Number>{ T [] nums;
Stats ( T [] o ){ nums = o ; }
double average(){ double sum = 0.0 ; for( int i = 0 ; i < nums.length ; i++ ){ sum += nums[i].doubleValue(); } return sum / nums.length ; }
public static void main( String [] args ){ Integer [] inums = {1,2,3,4,5} ; Stats<Integer> iob = new Stats<Integer>( inums ) ; double v = iob.average() ;
Double [] dnums = {1.1,2.2,3.3,4.4,5.5,6.6}; Stats<Double> dob = new Stats<Double>( dnums ) ; double w = dob.average() ;
String [] snums = {"A","B","C","D","E","F"} ; Stats<String> sob = new Stats<String>( snums ) ; //Compile Error double s = sob.average() ; }}
=========================Writed by Jack.Hao on 2011.01.20========================
五、通配符参数
通配符参数由?来表示,它代表一个未知类型,通配符只匹配任一有效的类型参数
class Stats<?>
有上界通配符:< ? extends superclass >其中的superclass表示用作上界的类的名称。构成上界的类也包含在限制之内。
有下界通配符:< ? super subclass >只有subclass的超类才是可接受的参数。
=========================Writed by Jack.Hao on 2011.01.20========================
六、创建泛型方法
class GenMathDemo{ static <T,V extends T> boolean isIn(T x , V [] y ){ for( int i = 0 ; i < y.length ; i++ ){ if( x.equals(y[i])) return true ;
} return false ; }
public static void main( String [] args ) { Integer [] nums = {1,2,3,4,5} ; if( isIn(2,nums)){ System.out.println( "2 is in nums" ) ; }
String [] strs = {"one","two","three","four","five"} ; if( isIn("two",strs)){ System.out.println( "two is in strs" ) ; }
//if( isIn("two",nums)){ //Error,can't compile } }}
泛型方法的声明:static<T,V extends T>boolean isIn(T x,V [] y){...},类型参数在此方法的返回类型之前声明。类型V的上界是类型T,所以V必须与T是同一个类型或是它的子类。只能两个参数类型一致时候才可以正常调用。泛型方法可以是静态的也可以是非静态的,这一点没有任何的限制。
泛型方法的声明语法:<type-param-list>return-type method-name(param-list) { ...... }type-param-list是一个由逗号分隔的类型参数列表,泛型方法的类型参数列表位于返回类型前。
如果一个类不是泛型类,但是它的构造方法确可能是泛型方法class GenCons{ private double val ; <T extends Number> GenCons( T arg ){ val = arg.doubleValue() ; } }
=========================Writed by Jack.Hao on 2011.01.20========================
七、创建泛型接口
interface MinMax<T extends Comparable<T>> { T min() ; T max() ;}class MyClass<T extends Comparable<T>> implements MinMax<T> { T [] vals ; MyClass( T [] o ) { vals = o ; } public T min(){ T v = vals[0] ; for( int i = 1; i < vals.length ; i++ ){ if( vals[i].compareTo(V) < 0 ) v = vals[i] ; } return v ; } public T min(){ T v = vals[0] ; for( int i = 1; i < vals.length ; i++ ){ if( vals[i].compareTo(V) > 0 ) v = vals[i] ; } return v ; }
public static void main( String [] args ){ Integer [] inums = {3,5,2,1,4,6} ;
Character [] chs = { 'b' , 'q' , 'e' , 'a' , 'd' , } ; MyClass<Integer> iob = new MyClass<Integer>( inums ) ; MyClass<Character> cob = new MyClass<Character>( chs ) ; System.out.println( "Max Integer:" + iob.max() ) ; System.out.println( "Min Integer:" + iob.max() ) ; System.out.println( "Max Character:" + iob.max() ) ; System.out.println( "Min Character:" + iob.max() ) ;
}
}
泛型接口的声明:interface MinMax<T extends Comparable<T>{ ...... } 类型参数为T,必须扩展自Comparable<T>接口,标识此类型数据是可比较的。
泛型接口实现类的声明:class MyClass<T extends Comparable<T>> implements MinMax<T> { ...... },注意:限制一旦建立,就不需要在implements子句中再指定一次了,如果指定了是不正确的。编译时会发生错误:class MyClass<T extends Comparable<T>> implements MinMax<T extends Comparable<T>> { ...... } //Error
如果一个类实现一个泛型接口,那么此类也通常也会定义为泛型类(推荐)。也可以不将实两类定义为泛型,但必须指定明确的类类型:class MyClass implements MinMax<T> { ...... } //Errorclass MyClass implements MinMax<Integer> { ...... } //OK
泛型接口的语法:interface interface-name<type-param-list>{ ...... } //type-param-list是由逗号分隔的参数列表,在实现泛型接口时,必须指定类型参数class class-name<type-param-list> implements interface-name<type-arg-list> { ...... }
=========================Writed by Jack.Hao on 2011.01.20========================
八、原始与遗留代码
因为泛型是一个新的特性,所以,必须能留保证现有的泛型机制要兼容历史遗留代码。为了能够兼容历史代码,Java允许一个泛型类在使用时不带任何类型参数,也就是创建一个该类的原始类型。使用原始类型的同时,也丢失了泛型的类型安全。class Gen<T> { T ob ; Gen( T o ){ ob = o ; } T getob(){ return ob ; }
public static void main( String [] args ){ Gen raw = new Gen( new Double( 99.9 ) ) ; Gen<String> strob = new Gen<String>( "Test" ) ; Gen<Integer> iob = new Gen<Integer>( new Integer( 100 ) ) ; double d = ( Double ) raw.getob() ; int i = ( Integer ) raw.getob() ; //编译通过,运行时错误 strob = raw ;
String str = strob.getob() ; //编译通过,运行时错误 raw = iob ; d = ( Double ) raw.getob() ; //编译通过,运行时错误 }}
原始类型不是类型安全的,可以把Gen对象的任何类型的引用赋值给原始类型变量。反之,也可以把原始Gen对象的引用赋给特定Gen类型的变量,但这两种操作都潜在的不安全因素。由于原始类型的潜在危险性,在编译时会出现警告信息。
注:原始类型是一种过渡特性,新编写的代码坚绝不要再使用。
=========================Writed by Jack.Hao on 2011.01.20========================
九、泛型类层次
和非泛型类一样,泛型类也存在层次,因此泛型类也可以作为超类或子类。泛型类的层次与非泛型类层次的区别在于:泛型类层次全部子类必须将泛型超类需要的类型参数沿层次向上传递。与构造函数参数必须沿层次向上传递类似。
class Gen<T>{ T ob ; Gen( T o ) { ob = o ; }}class Gen2<T> extends Gen<T> { Gen2( T o ){ super( o ) ; }}
Gen2扩展了泛型类Gen,类型参数T由Gen2指定,它传递给extends子句中的Gen,意味着传递给Gen2的任一种类型也会传递给Gen。类型参数T除了传递给Gen,Gen2中不使用类型参数T。因此,即使一个泛型超类的子类不需要泛型,它仍然要为它的泛型超类指定所需的类型参数。当然,子类也可以添加属于自己的类型参数:class Gen2<T,V> extends Gen<T>
1)泛型子类
一个非泛型类成为一个泛型子类的超类,是符合规范的
class NonGen{ int num ; NonGen( int i ){ num = i ; }}class Gen<T> extends NonGen{ T ob ; Gen( T o , int i ){ super( i ) ; ob = o ; }}
2)泛型类的比较
instanceof可应于泛型类的对象。
Gen<Integer> iob = new Gen<Integer>( 100 ) ;Gen2<Integer> iob2 = new Gen2<Integer>( 99 ) ;Gen2<String> strob = new Gen2<String>( "Test" ) ;if( iob2 instanceof Gen2<?> ) ...... // TRUEif( iob2 instanceof Gen<?> ) ...... // TRUEif( strob instanceof Gen2<?> ) ...... // TRUEif( strob instanceof Gen<?> ) ...... // TRUEif( iob instanceof Gen2<?> ) ...... // FALSEif( iob instanceof Gen<?> ) ...... // TRUE
3)泛型类的强制类型转换
虽然可以将泛型类的一个实例强制类型转换为泛型类的另一个实例,但是它们必须是相互兼容的才可以。(Gen<Integer>iob2) // OK
( Gen<Long> iob2 ) // WRONG 因为iob2不是Gen<Long>的实例
4)重写泛型类中的方法
泛型类中的方法与普通方法一样可以被重写
=========================Writed by Jack.Hao on 2011.01.20========================
十、擦拭
新增泛型特性很重要的一个约束前提:泛型代码必须同先前存在的非泛型代码兼容。任何对Java语言或JVM的语法变动,都应当避免旧代码的崩溃。Java通过“擦拭”在执行泛型同时此前提。
擦拭工作原理:当Java代码被编译时,全部泛型信息被移去,也就是使用类型参数来替换它们的界限类型(如果没有指定界限,界限类型为Object),然后运用相应的强制类型转换(由类型参数决定)以维持与类型参数指定的类型兼容。编译器会强制这种类型兼容。对泛型来说,这种方法意味着运行时不存在类型参数,它们仅是一种源代码机制。在运行时,只有原始类型实际存在。
class Gen<T>{ T ob ; Gen( T o ){ ob = o ; } T getob() { return ob ; }}
class GenStr<T extends String>{ T str ; GenStr( T o ){ str = o ; } T getstr(){ return str ; }}当这两个类编译完后,如变成如下形式:
class Gen extends java.lang.Object{ java.lang.Object ob ; Gen( java.lang.Object ) ; java.lang.Object getob() ;}
class GenStr extends java.lang.Object{ java.lang.String str ; GenStr( java.lang.String ) ; java.lang.String getstr() ;}
class GenTypeDemo{ public static void main( String [] args ){ Gen<Integer> iob = new Gen<Integer>( 99 ) ; Gen<Float> fob = new Gen<Float>( 99.9F ) ; System.out.println( iob.getClass().getName() ) ; // Gen 而不是Gen<Integer> System.out.println( fob .getClass().getName() ) ; // Gen 而不是Gen<Float> }}
=========================Writed by Jack.Hao on 2011.01.20========================
十一、模糊错误
引入泛型时,会导致一种新型错误的出现:模糊性。这种模糊错误的产生正是由于擦拭而引起的
class MyGenClass<T,V>{ T ob1 ; T ob2 ; void set( T o ){ ob1 = o ; } void set( V o ){ ob2 = o ; }}以上代码看起来很合理,T、V表示不同的类型参数。MyGenClass<String,Integer> obj1 = new MyGenClass<String,Integer>() ; // OK obj1.set( 123 ) ;obj1.set( "123" ) ;MyGenClass<String,String> obj2 = new MyGenClass<String,String>() ; // Error,因为此时编译器不知道该调那个set方法obj2.set( "123" ) ;obj2.set( "456" ) ;
解决模糊错误的方法:重构代码,提供方法重载形式或更换方法名
=========================Writed by Jack.Hao on 2011.01.20========================
十二、泛型的局限
1)类型参数不能被实例化
class Gen<T>{ T ob ; Gen(){ ob = new T () ; //非法的 }}
因为,T运行时不存在,编译器无法知道应该创建哪种类型的对象。
2)静态成员局限
所有的静态成员不能使用泛型类所声明的类型参数。
class Wrong<T>{ static T ob ; // Wrong,no static variables of type T static T getob(){ // Wrong,no static method can use T return ob ; }}
但是可以声明静态泛型方法,这些方法定义有它们自己的类型参数。
3)泛型类数组局限
不能实例化原始类型是类型参数的数组
class Gen<T extends Number>{ T ob ; T vals [] ; //OK Gen( T o , T [] nums ){ ob = o ; vals = new T [10] ; // Error valss = nums ; // OK }
public static void main( String [] args ){ Integer [] n = {1,2,3,4,5} ; Gen<Integer> iob = new Gen<Integer>( 50 , n ) ; Gen<Integer> [] gens = new Gen<Integer>[ 10 ] ; // Error ,不能编译 Gen<?> gens = new Gen<?>[ 10 ] ; // OK }}
Gen<Integer> [] gens = new Gen<Integer>[ 10 ] ;特定泛型类型的数组绝对不允许使用,因为它们会损害类型安全。
Gen<?> gens = new Gen<?>[ 10 ] ; 此方法优于原始类型的数组,因为至少一些类型检验会被强制执行。
4)泛型类异常局限
泛型类不能扩展Throwable,意味着不能创建泛型异常类