java基礎-泛型舉例詳解


泛型

  泛型是JDK5.0增加的新特性,泛型的本質是參數化類型,即所操作的數據類型被指定為一個參數。這種類型參數可以在類、接口、和方法的創建中,分別被稱為泛型類、泛型接口、泛型方法。

一、認識泛型

  在沒有泛型之前,通過對類型Object的引用來實現參數的"任意化",但"任意化"帶來的缺點是需要顯示的強制類型轉換,此種轉換要求開發者對實際參數類型預知的情況下進行,對於強制轉換錯誤的情況,編譯器可能不會提示錯誤,但在運行時會出現異常,這是一個安全隱患。

  舉例:不使用泛型實現參數化類型

 1 package generic;
 2 
 3 public class NoGeneric {
 4     private Object ob;    //定義通用類型成員
 5     public NoGeneric(Object ob) {
 6         this.ob = ob;
 7     }
 8     public Object getOb() {
 9         return ob;
10     }
11     public void setOb(Object ob) {
12         this.ob = ob;
13     }
14     public void showType() {
15         System.out.println("實際類型是:"+ob.getClass().getName());
16     }
17 }
18 
19 package generic;
20 
21 public class NoGenericDemo {
22 
23     public static void main(String[] args) {
24         // TODO 自動生成的方法存根
25         //定義類NoGener的一個Integer版本
26         NoGeneric intob = new NoGeneric(new Integer(66));
27         intob.showType();
28         int i = (Integer)intob.getOb();
29         System.out.println("value="+ i);
30         System.out.println("-----------------------------");
31         //定義類NoGeneric的一個String版本
32         NoGeneric strob = new NoGeneric(new String("hello"));
33         strob.showType();
34         String s = (String)strob.getOb();
35         System.out.println("value="+ s);
36     }
37 }

   執行結果為:

實際類型是:java.lang.Integer
value=66
-----------------------------
實際類型是:java.lang.String
value=hello

 

 

   上面的實例有兩點需要注意:首先如下語句

String s = (String)strob.getOb();

 

  在使用時必須明確指定返回對象需要被強制轉化的類型為String,否則無法編譯通過;其次,由於intob和strob都屬於NoGeneric的類型,假如執行如下語句

intob = strob;

 

  此種賦值,語法上是合法的,而在語義上是錯誤的,對於這種情況,只有在運行時才會出現異常,使用泛型就不會出現上述錯誤,泛型的好處就是在編譯期 檢查類型,捕捉類型不匹配錯誤,並且所有強制轉換都是自動和隱式的,提高代碼的重用率.

  舉例 2:使用泛型使用泛型實現參數實例化類型

package generic;

public class Generic<T> {
    private T ob; //定義泛型成員變量 
    public Generic(T ob) {
        this.ob = ob;
    }
    public T getOb() {
        return ob;
    }
    public void setOb(T ob) {
        this.ob = ob;
    }
    public void showType() {
        System.out.println("實例類型為:" + ob.getClass().getName());
    }
}

package generic;

public class GenericDemo {

    public static void main(String[] args) {
        // TODO 自動生成的方法存根
        //定義泛型Generic的一個Integer的版本
        Generic<Integer> intob = new Generic<Integer>(88);
        intob.showType();
        int i = intob.getOb();
        System.out.println("value=" + i);
        System.out.println("----------------------");
        //定義泛型Generic的一個String版本
        Generic<String> strob = new Generic<String>("hello");
        strob.showType();
        String s = strob.getOb();
        System.out.println("value=" + s);
    }
}

  運行結果為:

實例類型為:java.lang.Integer
value=88
----------------------
實例類型為:java.lang.String
value=hello

 

  在引入泛型的前提下,如果再次執行

intob = strob;

 

  將提示錯誤,編譯無法通過

二、泛型定義

  泛型的語法可歸納為

class class-name <type-param-list>{//......}

 

  實例化泛型的語法為:

class-name <type-param-list> obj =  new class-name<type-param-list>(cons-arg-list);

 

  type-param-list用於指明當前泛型類可接受的類型參數占位符的個數;   如:

class Generic<T>{//......}

 

  這里的T是類型參數的名稱,並且只允許傳一個類型參數給Generic類,在創建對象時,T用作傳遞 給Generic的實際類型的占位符,每當聲明類型參數時,只需用目標類型替換T即可.   如:

Generic <Integer> intob; 

 

  聲明對象時占位符T用於指定實際類型,如果傳遞實際類型為Integer,屬性ob就是Integer類型,類型T還可以指定方法的返回類型     如:

public T getOb(){
      return ob;
}

   理解泛型有三點需要注意:

    1、泛型的類型參數只能為類類型(包括自定義類),不能是基本數據類型。

    2、同一種泛型可以對應多個版本(因為類型參數時不確定的)、不同版本的泛型類實例是不兼容的。

    3、泛型的類型參數可以有多個。

    注意   根據慣例,泛型類定義時通常使用一個唯一的大寫字母表示一個類型參數.

三、有界類型

   定義泛型類時,可以向類型參數指定任何類型信息,特別是集合框架操作中,可以最大限度地提高適用范圍,但有時候需要對類型參數的取值進行一定程度的限制,以使數據具有可操作性.

   為了處理這種情況,java提供了有界類型,.在指定類型參數時可以使用extends關鍵字限制此類型參數代表的類必須是繼承自指定父類或父類本身.

  使用extends關鍵字實現有界類型泛型類的定義

package generic;

public class BoundGeneric<T extends Number> {
    //定義泛型數組
    T[] array;
    public BoundGeneric(T[] array) {
        this.array = array;
    }
    //計算總和
    public double sum() {
        double sum = 0.0;
        for(T t : array) {
            sum = sum + t.doubleValue();
        }
        return sum;
    }
}

 

  BoundGeneric類的定義中,使用extends將T的類型限制為Number類及其子類,故可以再定義過程中調用Number類的doubleValue方法,現在分別指定Integer,double,String類型作為類型參數,測試BoundGeneric:

package generic;

public class BoundGenericDemo {

    public static void main(String[] args) {
        // TODO 自動生成的方法存根
        //使用整形數組構造泛型對象
        Integer[] intArray = {1, 2, 3, 4};
        BoundGeneric<Integer> iobj = new BoundGeneric<Integer>(intArray);
        System.out.println("iobj的和為:" + iobj.sum());
        
        //使用Double型數組構造泛型對象
        Double[] douArray = {1.2, 2.3, 3.4, 4.5};
        BoundGeneric<Double> dobj = new BoundGeneric<Double>(douArray);
        System.out.println("dobj的和為:" + dobj.sum());
        
        String[] strArray = {"str1","str2"};
        //下面的語句將會報錯,String不是Number的子類
        //BoundGeneric<String> sobj = new BoundGeneric<String>(strArray);
    }
}

 

  運行結果為:

iobj的和為:10.0
dobj的和為:11.4

  注:在使用extends(如:T extends someClass)聲明的泛型類進行實例化時允許傳遞的參數類型為:如果someClass是類,可以傳遞someClass本身及其子類;如果someClass接口可以傳遞實現接口的類

四、通配符

  首先在說通配符之前先看一下這段代碼:使用前面定義的Generic類.

package generic;

public class WildcarDemo {
    public static void func(Generic <Object> g) {
        //...
    }
    public static void main(String args[]) {
        Generic <Object> obj = new Generic<Object>(12);
        func(obj);
        Generic<Integer> iobj = new Generic<Integer>(12);
        //這里講產生一個錯誤:類型 WildcarDemo 中的方法 func(Generic<Object>)對於參數(Generic<Integer>)不適用
        //func(iobj);
    }
}

 

  上述代碼的func()方法的創建意圖是能夠處理各種類型參數的Generic對象,因為Generic是泛型,所以在使用時需要為其指定具體的參數化類型Object,看似不成問題,

  但在

func(iobj);

 

  處產生一個編譯錯誤,因為func定義過程中以明確聲明的Generic的類型參數為Object,這里試圖將Generic<Integer>類型的對象傳遞給func()方法,類型不匹配導致編譯錯誤.這種情況可以使用通配符解決.通配符由"?"來表示,它代表一個未知類型

package generic;

public class WildcarDemo2 {
    public static void func(Generic <?> g) {
        //...
    }
    
    public static void main(String args[]) {
        Generic<Object> obj = new Generic<Object>(12);
        func(obj);
        
        Generic<Integer> iobj = new Generic<Integer>(12);
        func(iobj);
    }
}

 

  上述代碼,在采用了通配符后語句將無誤的編譯,運行.

  在通配符使用的過程中,也可通過extends關鍵字限定通配符的界定的類型參數的范圍.

package generic;

public class WildcarDemo3 {
    public static void func(Generic <? extends Number> g) {
        //...
    }
    
    public static void main(String args[]) {
        Generic<Object> obj = new Generic<Object>(12);
        //這里將產生一個錯誤:類型 WildcarDemo3 中的方法 func(Generic<? extends Number>)對於參數(Generic<Object>)不適用
        //func(obj);
        
        Generic<Integer> iobj = new Generic<Integer>(12);
        func(iobj);
    }
}

 

五、泛型的局限性

  java並沒有真正實現泛型,是編譯器在編譯的時候在字節碼上做了手腳(稱為擦除). 這種實現理念在成java泛型本身有很多漏洞, 為了避免這些問題java對泛型的使用上做了一些約束,但不可避免的還是有一些問題存在.多數的限制都是由類型擦除引起的.

  1、泛型類型不能被實例化

public class Gen<T>{
    T ob;
    public Gen(){
        ob = new T();
    }    
}

 

  Gen<T>構造器是非法的,類型擦除將變量T替換成Object,但這段代碼的本意肯定不是調用new Object().類似:如

public <T> T[]build (T[] a){
    T [] array = new T[2];  
  //... }

 

  類型擦除會讓這個方法總是構造一個Object[2]數組,但是可以通過調用Class.newInstance和Array.newInstance方法,利用反射構造泛型對象和數組

2、數組

  不能實例化數組如:

T[] vals;
vals = new T[10];

 

  因為T在運行時時不存在的,編譯器無法知道實際創建那種類型的數據.

  其次,不能創建一個類型特定的泛型引用的數組    如:

Gen<String> []arrays = new Gen<String>[100];

 

  上面的代碼會損害類型安全

  如果使用通配符,就可以創建泛型類型的引用數組

Gen<?> []arrays = new Gen<?>[10];

 

3、怒能用類型參數替換基本類型

  因為擦除類型后原先的類型參數被Object或者限定類型替換,而基本類型是不能被對象所存儲的,可以使用基本類型的包裝類來解決此問題

4、異常

  不能拋出也不能捕獲泛型類的異常對象,使用泛型類來擴展Throwable也是非法的. 如:

public class GenericException <T> extends Exception{
    //泛型類無法繼承Throwable
}

 

  不能再catch子句中使用類型參數,例如下面的方法將不能編譯

    public static <T extends Throwable> void doWork(Class<T> t) {
    try {
        //...
    }catch(Throwable realCause) {
        //...
    }

 

  但是在異常聲明時可以使用類型參數,如:

    public static <T extends Throwable> void doWork(T t) throws T {
        try {
            //...
        }catch(Throwable realCause) {
            throw t;
        }
    }

 

5、靜態成員

  不能在靜態變量或者靜態方法中引用類型參數  如:

public class Gen<T>{
    static T ob;
    static T getOb() {
        return ob;
    }
}

 這些均參考自“Java SE程序設計”,算是做個筆記,以后忘了可以翻閱一下,寫在自己的隨筆中,也希望可以幫助更多的人。如有侵權,請聯系本人刪除

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM