java泛型理解一

在Java SE 1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實現參數的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。對於強制類型轉換錯誤的情況,編譯器可能不提示錯誤,在運行的時候纔出現異常,這是一個安全隱患。  泛型的好處是在編譯的時候檢查類型安全,並且所有的強制轉換都是自動和隱式的,提高代碼的重用率。

規則和限制

1、泛型的類型參數只能是類類型(包括自定義類),不能是簡單類型。  2、同一種泛型可以對應多個版本(因爲參數類型是不確定的),不同版本的泛型類實例是不兼容的。  3、泛型的類型參數可以有多個。  4、泛型的參數類型可以使用extends語句,例如<T extends superclass>。習慣上稱爲“有界類型”。  5、泛型的參數類型還可以是通配符類型。例如Class<?> classType = Class.forName("java.lang.String");  泛型還有接口、方法等等,內容很多,需要花費一番功夫才能理解掌握並熟練應用。在此給出我曾經瞭解泛型時候寫出的兩個例子(根據看的印象寫的),實現同樣的功能,一個使用了泛型,一個沒有使用,通過對比,可以很快學會泛型的應用,學會這個基本上學會了泛型70%的內容。  例子一:使用了泛型  public class Gen<T> {  private T ob; //定義泛型成員變量  public Gen(T ob) {  this.ob = ob;  }  public T getOb() {  return ob;  }  public void setOb(T ob) {  this.ob = ob;  }  public void showType() {  System.out.println("T的實際類型是: " + ob.getClass().getName());  }  }  public class GenDemo {  public static void main(String[] args){  //定義泛型類Gen的一個Integer版本  Gen<Integer> intOb=new Gen<Integer>(88);  intOb.showType();  int i= intOb.getOb();  System.out.println("value= " + i);  System.out.println("----------------------------------");  //定義泛型類Gen的一個String版本  Gen<String> strOb=new Gen<String>("Hello Gen!");  strOb.showType();  String s=strOb.getOb();  System.out.println("value= " + s);  }  }  例子二:沒有使用泛型  public class Gen2 {  private Object ob; //定義一個通用類型成員  public Gen2(Object ob) {  this.ob = ob;  }  public Object getOb() {  return ob;  }  public void setOb(Object ob) {  this.ob = ob;  }  public void showTyep() {  System.out.println("T的實際類型是: " + ob.getClass().getName());  }  }  public class GenDemo2 {  public static void main(String[] args) {  //定義類Gen2的一個Integer版本  Gen2 intOb = new Gen2(new Integer(88));  intOb.showTyep();  int i = (Integer) intOb.getOb();  System.out.println("value= " + i);  System.out.println("---------------------------------");  //定義類Gen2的一個String版本  Gen2 strOb = new Gen2("Hello Gen!");  strOb.showTyep();  String s = (String) strOb.getOb();  System.out.println("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="+(Object)objFoo.getX());  }  }  運行結果如下:  strFoo.getX=Hello Generics!  douFoo.getX=33.0  objFoo.getX=java.lang.Object@19821f  解說:在Java 5之前,爲了讓類有通用性,往往將參數類型、返回類型設置爲Object類型,當獲取這些返回類型來使用時候,必須將其“強制”轉換爲原有的類型或者接口,然後纔可以調用對象上的方法。

3、Java1.5泛型來實現

強制類型轉換很麻煩,我還要事先知道各個Object具體類型是什麼,才能做出正確轉換。否則,要是轉換的類型不對,比如將“Hello Generics!”字符串強制轉換爲Double,那麼編譯的時候不會報錯,可是運行的時候就掛了。那有沒有不強制轉換的辦法----有,改用 Java5泛型來實現。  public 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;  }  }  public class GenericsFooDemo {  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  objFoo.getX=java.lang.Object@19821f  和使用“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類的類型。  下面繼續對上面的例子改進,我只要實現了集合接口的類型:  public class CollectionGenFoo<T extends Collection> {  private T x;  public CollectionGenFoo(T x) {  this.x = x;  }  public T getX() {  return x;  }  public void setX(T x) {  this.x = x;  }  }  實例化的時候可以這麼寫:  public class CollectionGenFooDemo {  public static void main(String args[]) {  CollectionGenFoo<ArrayList> listFoo = null;  listFoo = new CollectionGenFoo<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接口。那麼上面實現的方式可以寫爲:  public class CollectionGenFooDemo {  public static void main(String args[]) {  CollectionGenFoo<ArrayList> listFoo = null;  listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());  //現在不會出錯了  CollectionGenFoo<? extends Collection> listFoo1 = null;  listFoo=new CollectionGenFoo<ArrayList>(new ArrayList());  System.out.println("實例化成功!");  }  }  注意:  1、如果只指定了<?>,而沒有extends,則默認是允許Object及其下的任何Java類了。也就是任意類。  2、通配符泛型不單可以向下限制,如<? extends Collection>,還可以向上限制,如<? super Double>,表示類型只能接受Double及其上層父類類型,如Number、Object類型的實例。  3、泛型類定義可以有多個泛型參數,中間用逗號隔開,還可以定義泛型接口,泛型方法。這些都泛型類中泛型的使用規則類似。

泛型方法

是否擁有泛型方法,與其所在的類是否泛型沒有關係。要定義泛型方法,只需將泛型參數列表置於返回值前。如:  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方法需要使用泛型能力,必須使其成爲泛型方法。
發佈了75 篇原創文章 · 獲贊 32 · 訪問量 80萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章