1. 程式人生 > >Potter:I am not for learning and learning, but for study and work

Potter:I am not for learning and learning, but for study and work

看不少文章都沒有弄得這個泛型到底是做什麼的,怎麼用?下面這篇文章就用最通俗的話來介紹...一看就明白

規則和限制

       1、泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別。

  2、同一種泛型可以對應多個版本(因為引數型別是不確定的),不同版本的泛型類例項是不相容的。

  3、泛型的型別引數可以有多個。

  4、泛型的引數型別可以使用extends語句,例如<T extends superclass>。習慣上稱為“有界型別”。

  5、泛型的引數型別還可以是萬用字元型別。例如Class<?> classType = Class.forName("java.lang.String");

  泛型還有介面、方法等等,內容很多,需要花費一番功夫才能理解掌握並熟練應用。在此給出我曾經瞭解泛型時候寫出的兩個例子(根據看的印象寫的),實現同樣的功能,一個使用了泛型,一個沒有使用,通過對比,可以很快學會泛型的應用,學會這個基本上學會了泛型70%的內容。

例子:
/**
 *@Description: 
 *@author Potter   
 *@date 2012-3-5 下午10:16:28
 *@version V1.0   
 */
public class Gen<T> {
	private T ob;
	
	public Gen(T ob){
		this.ob=ob;
	}
	
	public void showType(){
		System.out.println("T的實際型別:"+ob.getClass());
	}
	
	public T getOb() {
		return ob;
	}
	public void setOb(T ob) {
		this.ob = ob;
	}
	
}

class GenDemo{
	public static void main(String[] args){
		Gen<Integer> intOb=new Gen<Integer>(88);
		intOb.showType();
		int i=intOb.getOb();
		System.out.println("i value="+i);
		System.out.println("---------");
		Gen<String>strOb=new Gen<String>("hello Gen!");
		strOb.showType();
		String s=strOb.getOb();
		System.out.println("s value="+s);
	}
	
}


例子二:

/**
 *@Description: 
 *@author Potter   
 *@date 2012-3-5 下午10:24:49
 *@version V1.0   
 */
public class Gen2 {
	private Object ob;
	
	public Gen2(Object ob){
		this.ob=ob;
	}
	
	public void showType(){
		System.out.println("T的實際型別是:"+ob.getClass().getName());
	}
	
	public Object getOb() {
		return ob;
	}
	public void setOb(Object ob) {
		this.ob = ob;
	}
}

class GenDemo2{
	public static void main(String[] args){
		Gen2 intOb=new Gen2(new Integer(88));
		intOb.showType();
		int i=(Integer)intOb.getOb();
		System.out.println("i value="+i);
		System.out.println("-----------");
		Gen2 strOb=new Gen2("Hello Gen!");
		strOb.showType();
		String s=(String)strOb.getOb();
		System.out.println("s value="+s);
	}
}

執行結果:

  兩個例子執行Demo結果是相同的,控制檯輸出結果如下:

  T的實際型別是:

  java.lang.Integer

  value= 88

  ----------------------------------

  T的實際型別是: java.lang.String

  value= Hello Gen!

  Process finished with exit code 0

  看明白這個,以後基本的泛型應用和程式碼閱讀就不成問題了。

逐漸深入泛型

1、沒有任何重構的原始程式碼:

      有兩個類如下:要構造兩個類的物件,並打印出各自的成員x

public class StringFoo {
    private String x; 
    public StringFoo(String x) {
    	this.x=x;  
    } 
    public String getX() {
    	return x;  
    } 
    public void setX(String x) {
	this.x = x;   
    }
}
public class DoubleFoo {
	private Double x;
    public DoubleFoo(Double x) {
	this.x=x;   
    }
    public Double getX() {
	return x;   
    }
    public void setX(Double x) {
	this.x = x;   
    }   
} 

以上的程式碼實在無聊,就不寫如何實現了。

2、對上面的兩個類進行重構,寫成一個類

因為上面的類中,成員和方法的邏輯都一樣,就是型別不一樣,因此考慮重構。Object是所有類的父類,因此可以考慮用Object做為成員型別,這樣就可以實現通用了,實際上就是“Object泛型”,暫時這麼稱呼。

public class ObjectFoo { 
	private Object x;
	public ObjectFoo(Object x){
		this.x=x;
	}
	
	public Object getX() {
		return x;
	}
	public void setX(Object x) {
		this.x = x;
	}
	
}

寫出Demo方法如下:

public  class ObjectFooDemo{
 public static void main(String[] args){
  ObjectFoo strFoo=new ObjectFoo("Hello generics!");
  ObjectFoo douFoo=new ObjectFoo(new Double("33"));
  ObjectFoo objFoo = new ObjectFoo(new Object()); 
  System.out.println("strFoo.getX="+(String)strFoo.getX()); 
  System.out.println("douFoo.getX="+(Double)douFoo.getX());
  System.out.println("objFoo.getX="+objFoo.getX()); 
 }

運算結果:

解說:在Java 5之前,為了讓類有通用性,往往將引數型別、返回型別設定為Object型別,當獲取這些返回型別來使用時候,必須將其“強制”轉換為原有的型別或者介面,然後才可以呼叫物件上的方法。

3、java1.5泛型來實現
強制型別轉換很麻煩,我還要事先知道各個Object具體型別是什麼,才能做出正確轉換。否則,要是轉換的型別不對,比如將“Hello Generics!”字串強制轉換為Double,那麼編譯的時候不會報錯,可是執行的時候就掛了。那有沒有不強制轉換的辦法----有,改用 Java5泛型來實現。

class GenericsFoo<T>{ 
	private T x;
	public GenericsFoo(T x){
		this.x=x;
	}
	
	public T getX() {
		return x;
	}
	public void setX(T x) {
		this.x = x;
	}
	
}

 class ObjectFooDemo{
	public static void main(String[] args){
		GenericsFoo<String> strFoo=new GenericsFoo<String>("Hello Generics!"); 
		GenericsFoo<Double> douFoo=new GenericsFoo<Double>(new Double("33")); 
		GenericsFoo<Object> objFoo=new GenericsFoo<Object>(new Object()); 
		System.out.println("strFoo.getX="+strFoo.getX()); 
		System.out.println("douFoo.getX="+douFoo.getX());
		System.out.println("objFoo.getX="+objFoo.getX()); 
	}
}


執行結果:

strFoo.getX=Hello Generics!
douFoo.getX=33.0
[email protected]
和使用“Object泛型”方式實現結果的完全一樣,但是這個Demo簡單多了,裡面沒有強制型別轉換資訊。

下面解釋一下上面泛型類的語法:

使用<T>來宣告一個型別持有者名稱,然後就可以把T當作一個型別代表來宣告成員、引數和返回值型別。

當然T僅僅是個名字,這個名字可以自行定義。

class GenericsFoo<T> 聲明瞭一個泛型類,這個T沒有任何限制,實際上相當於Object型別,實際上相當於 class GenericsFoo<T extends Object>。

與Object泛型類相比,使用泛型所定義的類在宣告和構造例項的時候,可以使用“<實際型別>”來一併指定泛型型別持有者的真實型別。類如

GenericsFoo<Double> douFoo=new GenericsFoo<Double>(new Double("33"));

當然,也可以在構造物件的時候不使用尖括號指定泛型型別的真實型別,但是你在使用該物件的時候,就需要強制轉換了。比如:GenericsFoo douFoo=new GenericsFoo(new Double("33"));

    實際上,當構造物件時不指定型別資訊的時候,預設會使用Object型別,這也是要強制轉換的原因。

泛型的高階應用

1、在上面的例子中,由於沒有限制class GenericsFoo<T>型別持有者T的範圍,實際上這裡的限定型別相當於Object,這和“Object泛型”實質是一樣的。限制比如我們要限制T為集合介面型別。只需要這麼做:

  class GenericsFoo<T extends Collection>,這樣類中的泛型T只能是Collection介面的實現類,傳入非Collection介面編譯會出錯。

  注意:<T extends Collection>這裡的限定使用關鍵字 extends,後面可以是類也可以是介面。但這裡的extends已經不是繼承的含義了,應該理解為T型別是實現Collection介面的型別,或者T是繼承了XX類的型別。

  下面繼續對上面的例子改進,我只要實現了集合介面的型別:

import java.util.ArrayList;
import java.util.Collection;

/**
 *@Description: 
 *@author Potter   
 *@date 2012-3-5 下午10:49:54
 *@version V1.0   
 */
public class CollectGenFoo<T extends Collection> {
	private T x;

	public CollectGenFoo(T x) {
		this.x=x;
	}
	
	public T getX() {
		return x;
	}

	public void setX(T x) {
		this.x = x;
	}
}

class CollectGenFooDemo{
	public static void main(String[]args){
		CollectGenFoo<ArrayList> listFoo=null;
		listFoo=new CollectGenFoo<ArrayList>(new ArrayList());		
		//出錯了,不讓這麼幹
		//CollectGenFoo<Collection> listFoo=null;
		//listFoo=new CollectGenFoo<ArrayList>(new ArrayList());
		// CollectionGenFoo<Collection> listFoo = null; 
		// listFoo=new CollectionGenFoo<ArrayList>(new ArrayList()); 
		System.out.println("例項化成功!"); 
	}
}


當前看到的這個寫法是可以編譯通過,並執行成功。可是註釋掉的兩行加上就出錯了,因為<T extends Collection>這麼定義型別的時候,就限定了構造此類例項的時候T是確定的一個型別,這個型別實現了Collection介面,但是實現 Collection介面的類很多很多,如果針對每一種都要寫出具體的子類型別,那也太麻煩了,我乾脆還不如用Object通用一下。別急,泛型針對這種情況還有更好的解決方案,那就是“萬用字元泛型”。

2、萬用字元泛型

為了解決型別被限制死了不能動態根據例項來確定的缺點,引入了“萬用字元泛型”,針對上面的例子,使用通配泛型格式為<? extends Collection>,“?”代表未知型別,這個型別是實現Collection介面。那麼上面實現的方式可以寫為:

class CollectGenFooDemo{
	public static void main(String[]args){
		CollectGenFoo<ArrayList> listFoo=null;
		listFoo=new CollectGenFoo<ArrayList>(new ArrayList());
		//CollectGenFoo<Collection> listFoo=null;
		//listFoo=new CollectGenFoo<ArrayList>(new ArrayList());
		
		CollectGenFoo<? extends Collection>listFoo1=null;
		listFoo1=new CollectGenFoo<ArrayList>(new ArrayList());
		System.out.println("例項化成功");
	}
}

注意:

  1、如果只指定了<?>,而沒有extends,則預設是允許Object及其下的任何Java類了。也就是任意類。

  2、萬用字元泛型不單可以向下限制,如<? extends Collection>,還可以向上限制,如<? super Double>,表示型別只能接受Double及其上層父類型別,如Number、Object型別的例項。

  3、泛型類定義可以有多個泛型引數,中間用逗號隔開,還可以定義泛型介面,泛型方法。這些都泛型類中泛型的使用規則類似。

泛型方法:

是否擁有泛型方法,與其所在的類是否泛型沒有關係。要定義泛型方法,只需將泛型引數列表置於返回值前。如:

/**
 *@Description: 
 *@author Potter   
 *@date 2012-3-5 下午11:56:51
 *@version V1.0   
 */
public class ExampleA {
	public <T> void f(T x){
		System.out.println(x.getClass().getName());
	} 
	
	public static void main(String[] args){
		ExampleA ea=new ExampleA();
		ea.f("");
		ea.f(10);
		ea.f('a');
		ea.f(ea);
	}
}

輸出結果:

  java.lang.String

  java.lang.Integer

  java.lang.Character

  ExampleA

  使用泛型方法時,不必指明引數型別,編譯器會自己找出具體的型別。泛型方法除了定義不同,呼叫就像普通方法一樣。

  需要注意,一個static方法,無法訪問泛型類的型別引數,所以,若要static方法需要使用泛型能力,必須使其成為泛型方法。