10. 泛型 Part 1 --- 學習筆記



通過本章的學習可以達到以下目標

  • 掌握泛型的基本原理及其應用
  • 掌握泛型通配符的使用
  • 指定泛型操作中的上限及下限
  • 在接口上應用泛型
  • 掌握泛型方法及泛型數組的使用
  • 如果需要更加合理的應用泛型,就需要結合類集框架及反射機制。

             所謂的泛型(Generics)就是指在對象建立時不指定類中屬性的具體類型,而是由外部在聲明及實例化對象時指定類型。


10.1 爲什麼使用泛型

應用實例:設計一個可以表示座標點的類,昨天由X和Y組成,座標的表示方法有以下三種:

  1. 整數表示: x = 10、y = 20
  2. 小數表示: x = 10.5、y = 20.6
  3. 字符串表示: x= "東經180度"、 y = "北緯210度"

由於x和y中所保存的數據類型有3種: int、float、String。 想要同時接收這3種數據類型,只能使用Object類。因爲Object類可以接收任何類型的數據,都會發生自動向上轉型操作。 但是當x、y的數據類型不一致的時候,則會產生一個運行時異常ClassCastException(編譯可以成功)

       ****************接收不同類型的數據類型。避免運行時產生ClassCastException異常******************


10.2 泛型應用

       10.2.1 泛型的基本應用

            泛型可以解決數據類型的安全性問題,其主要原理是在類聲明時通過一個標識標識類中某個屬性的類型或者是某個方法的返回值及參數類型。這樣在類聲明或對象的實例化時只要指定好需要的類型即可。

           泛型類的定義格式如下:

<span style="font-size:14px;color:#33cc00;">[訪問權限] class 類名稱<泛型類型標識1, 泛型類型標識2, ..., 泛型類型標識n>{
     [訪問權限] 泛型類型標識 變量名稱;
     [訪問權限] 泛型類型標識 方法名稱(){}
     [訪問權限] 返回值類型聲明 方法名稱(泛型類型標識 變量名稱){}
}</span>

           泛型對象的定義格式如下:

類名稱<具體類> 對象名稱 = new 類名稱<具體類>();

範例:聲明泛型

class Point<T>{         //此處可以是任意的標識符號,T是type的簡稱
     private T var;        //此變量的類型由外部決定
     public T getVar(){                //返回值的類型由外部決定
         return this.var;                 //設置的類型由外部決定
     }
     public void setVar(T var){
         this.var = var;
     }
}

    上面代碼中的Point類在聲明時使用了“<T>”的形式,T表示此類型是由外部調用本類時指定的,這裏使用任意的字母都可以,如“<A>”、“<B>”,之所以使用“<T>”是因爲T是type的縮寫,表示類型,這樣比較好理解。

    之後再類中定義的var屬性的類型也是T,這就表示var這個屬性的類型也是由外部來決定的,而不是固定的。

    同理,setter方法中的參數類型以及getter方法中的返回值類型也由外部設置。

範例:使用Point類將var的類型設置成整數

class Point<T>{
    private T var;
    public T getVar(){
        return this.var;
    }
    public void setVar(T var){
        this.var = var;
    }
}
public class GenericsDemo01{
    public static void main(String args[]){
        Point<Integer> p = new Point<Integer>();     //裏面的var類型爲Integer類型
        p.setVar(28);                                //設置數字,自動裝箱
        System.out.println(p.getVar() * 2);
    }
}

以上程序將Point類中的var屬性設置成Integer類型,所以在聲明及實例化對象的時候使用Point<Integer>。 這樣實際上Point類中的setter和getter方法就變成了以下形式:

public Integer getVar(){
     return this.var;
}
public void setVar(Integer var){
     this.var = var;
}
上面代碼只是對設置泛型之後的一種說明,一切操作都是按照數字的方式進行操作的。

範例:使用以上的Point類,將var設置成String類型

class Point<T>{
    private T var;
    public T getVar(){
        return this.var;
    }
    public void setVar(T var){
        this.var = var;
    }
}
public class GenericsDemo02{
    public static void main(String args[]){
        Point<String> p = new Point<String>();     //裏面的var類型爲String類型
        p.setVar("forfan06");                                //設置字符串,自動裝箱
        System.out.println(p.getVar().length());
    }
}

如果設置的內容與泛型所指定的類型不一樣,則在編譯時就會出錯!!!!

class Point<T>{
    private T var;
    public T getVar(){
        return this.var;
    }
    public void setVar(T var){
        this.var = var;
    }
}
public class GenericsDemo03{
    public static void main(String args[]){
        Point<Integer> p = new Point<Integer>();     //裏面的var類型爲Integer類型
        p.setVar("forfan06");                                //設置字符串類型,自動裝箱後與指定的Integer類型不一致
        System.out.println(p.getVar().length());
    }
}

編譯時出錯:

GenericsDemo03.java:13: error: method setVar in class Point cannot be applied to given types;
        p.setVar("forfan06");                                //設置字符串類型,自動裝箱後與指定的Integer類型不一致
         ^
  required: Integer
  found: String
  reason: actual argument String cannot be converted to Integer by method invocation conversion
  where T is a type-variable:
    T extends Object declared in class Point

       10.2.2 使用泛型修改代碼

             實現前面的問題,類Point是一個表示座標點的類x、y。  此時Point類中必須保證x、y座標的數據類型一致。 最好使用泛型。代碼如下:

class Point<T>{
    private T x;
    private T y;
    public T getX(){
        return this.x;
    }
    public T getY(){
        return this.y;
    }
    public void setX(T x){
        this.x = x;
    }
    public void setY(T y){
        this.y = y;
    }
}

public class GenericsDemo04{
    public static void main(String args[]){
        Point<Integer> p = new Point<Integer>();
        p.setX(17);                //設置整數,自動裝箱。
        p.setY(58);
        int x = p.getX();           //自動拆箱!!
        int y = p.getY();
        System.out.println("整數表示,x座標:" + x);
        System.out.println("整數表示,y座標:" + y);
    }
}

如果此時y座標指定的類型不是Integer,則將在編譯時就會出錯!!!

class Point<T>{
    private T x;
    private T y;
    public T getX(){
        return this.x;
    }
    public T getY(){
        return this.y;
    }
    public void setX(T x){
        this.x = x;
    }
    public void setY(T y){
        this.y = y;
    }
}

public class GenericsDemo05{
    public static void main(String args[]){
        Point<Integer> p = new Point<Integer>();
        p.setX(17);                //設置整數,自動裝箱。
        p.setY("forfan06");        //與Point聲明時的數據類型不一致,編譯通不過!!
        int x = p.getX();           //自動拆箱!!
        int y = p.getY();
        System.out.println("整數表示,x座標:" + x);
        System.out.println("整數表示,y座標:" + y);
    }
}

編譯出錯:

GenericsDemo05.java:22: error: method setY in class Point cannot be applied to given types;
        p.setY("forfan06");        //與Point聲明時的數據類型不一致,編譯通不過!!
         ^
  required: Integer
  found: String
  reason: actual argument String cannot be converted to Integer by method invocation conversion
  where T is a type-variable:
    T extends Object declared in class Point
1 error

編譯錯誤

  • 加入泛型的最大好處實際上就是避免了類轉換異常(ClassCastException)的發生,這樣使程序的操作更加安全!!!!

       10.2.3 泛型應用中的構造方法

            構造方法可以爲類中的屬性初始化,那麼如果類中的屬性通過泛型指定,而又需要通過構造設置屬性內容時,構造方法的定義與之前構造方法相同。不需要象聲明類那樣指定類型。具體格式如下:

[訪問權限] 構造方法([泛型類型 參數名稱]){

}

泛型類的構造方法與普通類的構造方法是一樣的,也就是說即使有泛型聲明,也只是在類的定義上聲明,而與構造方法的定義無關!!!

       10.2.4 指定多個泛型類型

          如果一個類中有多個屬性需要使用不同的泛型聲明,則可以在聲明類時指定多個泛型類型。

class Notepad<K, V>{                 //此處指定兩個泛型類型
    private K key;                   //變量key、value的類型都是由外部決定的
    private V value;
    public K getKey(){               //setter和getter方法
        return key;
    }
    public void setKey(K key){
        this.key = key;
    }
    public V getValue(){
        return value;
    }
    public void setValue(V value){
        this.value = value;
    }
}
public class GenericsDemo06{
    public static void main(String args[]){
        Notepad<String, Integer> t = null;               //定義兩個泛型類型的對象
        t = new Notepad<String, Integer>();              //裏面的key爲String,value爲Integer
        t.setKey("forfan06");                            //設置屬性內容,自動自動裝箱操作
        t.setValue(27);
        System.out.print("姓名:" + t.getKey());
        System.out.println(", 年齡:" + t.getValue());
    }
}


10.3 泛型的安全警告

                在泛型應用中最好在聲明類對象時就指定好其內部的數據類型,如Info<String>,如果不指定類型,這樣用戶在使用這樣的類時,就會出現不安全的警告信息。

範例:不指定範例類型

class Info<T>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo07{
    public static void main(String args[]){
        Info i = new Info();                    //警告,沒有指定泛型類型
        i.setVar("forfan06");
        System.out.println("內容:" + i.getVar());
    }
}

編譯時出現以下警告:

GenericsDemo07.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

      以上程序雖然在編譯時出現了警告,但是並不影響程序的運行,這是因爲在泛型的操作中爲了方便用戶的使用,就算沒有指定泛型程序也可以正常使用,而所有的類型統一使用Object進行接收,所以上面程序的var屬性實際上是Object類型的,也就是在定義時將泛型擦除了。以上程序類似於下面的定義:

public class GenericsDemo08{
    public static void main(String args[]){
        Info<Object> i = new Info<Object>();                    //指定爲Object爲泛型類型
        i.setVar("forfan06");
        System.out.println("內容:" + i.getVar());
    }
}

               GenericsDemo08沒有任何的意義,因爲就算不設置泛型也是Object,但這樣做的唯一好處就是,在編譯時警告信息消失了。


10.4 通配符

         以上的範例程序在操作時都設置了一個固定的類型,在泛型操作中也可以通過通配符接收任意指定泛型類型的對象。

       10.4.1匹配任意類型的通配符

           在開發中對象的引用傳遞是最常見的,但是如果在泛型類的操作中,在進行引用傳遞時泛型類型必須匹配纔可以傳遞,否則是無法傳遞的。

class Info<T>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo09{
    public static void main(String args[]){
        Info<String> i = new Info<String>();                    //指定String爲泛型類型
        i.setVar("forfan06");
        fun(i);                                                 //此處無法傳遞
    }
    public static void fun(Info<Object> temp){                  //此方法可以接收Object泛型類型的Info對象
        System.out.println("內容:" + temp);
    }
}

編譯時錯誤:

GenericsDemo09.java:17: error: method fun in class GenericsDemo09 cannot be applied to given types;
        fun(i);                                                 //此處無法傳遞
        ^
  required: Info
  found: Info
  reason: actual argument Info cannot be converted to Info by method invocation conversion
1 error

         此時儘管String是Object類的子類,但是在進行引用傳遞時也同樣無法進行操作。此時Info<String>、Info<Object>並無父類和子類的關係,不能夠完成轉型操作(向上轉型)。

         Java中引入了通配符“?”,來表示可以接收此類型的任意泛型對象。所以將GenericsDemo09代碼作如下修改(使用通配符?)

class Info<T>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo10{
    public static void main(String args[]){
        Info<String> i = new Info<String>();                    //指定String爲泛型類型
        i.setVar("forfan06");
        fun(i);                                                 //此處無法傳遞
    }
    public static void fun(Info<?> temp){                  //此方法可以接收任意Info泛型類型的對象
        System.out.println("內容:" + temp);
    }
}

在程序GenericsDemo10種的fun()方法使用了Info<?>的形式,表示可以接收任意的泛型類型對象,這樣做的話,fun()方法定義的就比較合理了。但是在使用以上語法時也要注意一點: 如果使用“?”接收泛型對象時,不能設置被泛型指定的內容。    但是可以設置爲null值得!!!!例如:

class Info<T>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo11{
    public static void main(String args[]){
        Info<?> i = new Info<String>();                    //使用“”接收泛型對象
        i.setVar("forfan06");                              //錯誤,無法設置
    }
}

編譯時得到錯誤:

GenericsDemo11.java:16: error: method setVar in class Info cannot be applied to given types;
        i.setVar("forfan06");                              //錯誤,無法設置
         ^
  required: CAP#1
  found: String
  reason: actual argument String cannot be converted to CAP#1 by method invocation conversion
  where T is a type-variable:
    T extends Object declared in class Info
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error

編譯錯誤

       10.4.2 受限泛型

             在引用傳遞中,在泛型操作中也可以設置一個泛型對象的範圍上限和範圍下限。

             範圍上限使用extends關鍵字聲明,表示參數化的類型可能是所指定的類型或者是此類型的子類;

             範圍下限使用super關鍵字聲明,表示參數化的類型可能是所指定的類型,或者是此類型的父類,或者是Object類。

<span style="font-size:14px;color:#6600cc;">設置上限</span>
聲明對象:  類名稱<? extends 類> 對象名稱
定義類: [訪問權限] 類名稱<泛型標識 extends 類>{}


<span style="font-size:14px;color:#6600cc;">設置下限</span>
聲明對象: 類名稱<? super 類> 對象名稱
<del><span style="color:#ff0000;">定義類:[訪問權限] 類名稱<泛型標識 super 類>{}               //這裏是錯誤的!!!!!!!!!!!!!!!!!!</span></del>

範例: 泛型的上限

class Info<T>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo12{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>();              //聲明Integer、Float泛型的對象
        Info<Float> i2 = new Info<Float>();
        i1.setVar(28);                                       //設置內容,自動裝箱
        i2.setVar(27.2f);
        fun(i1);
        fun(i2);
    }
    //接收Info對象,範圍上限設置爲Number類,所以只能接收數字類型的數據。此方法只能接收泛型爲Number類或者Number類型的子類。此時若傳遞一個String類的泛型對象,則編譯時將會出現錯誤!!!!Info<String> i3 = new Info<String>(); i3.setVar("forfan06"); fun(i3); 此時fun(i3)編譯會出錯
    public static void fun(Info<? extends Number> temp){
        System.out.print(temp + "、");
    }
}

也可以直接在類的聲明處指定泛型的上限範圍,如:

class Info<T extends Number>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo13{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>();
        System.out.println("內容: " + i1);
        Info<String> i2 = new Info<String>();                           //<del><span style="color:#cc0000;">編譯錯誤,聲明的是String類型的泛型對象,不是Number類的子類!!</span></del>!!
    }
}

範例: 泛型的下限   當使用的泛型只能在本類及其父類類型上應用時,就必須使用泛型的範圍下限進行配置:

class Info<T>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo14{
    public static void main(String args[]){
        Info<Object> i1 = new Info<Object>();              //聲明Object, String泛型的對象
        Info<String> i2 = new Info<String>();
        i1.setVar(new Object());                                       //設置內容,自動裝箱
        i2.setVar("forfan06");
        fun(i1);
        fun(i2);
        Info<Integer> i3 = new Info<Integer>();
        i3.setVar(27);
        fun(i3);                                    <span style="color:#ff0000;">//編譯出錯,不滿足下限要求</span>
    }
    //接收Info對象,範圍下限設置爲String類,只能接收String或其父類Object類型的泛型
    public static void fun(Info<? super String> temp){
        System.out.print("內容:" + temp);
    }
}

不能這樣子來指定泛型的下限:

<span style="font-size:14px;color:#cc0000;"><del> class A<T super String>{}</del></span>

原因如下:

Nitish B. 
Well bounded types cannot use "super" as every class's super is Object. Now if you say that its Object, it can be anything other than String which makes the bound useless. 
  
Koustav C.  
Hey Nitish, 
 Thanks for your quick response as always. But it will be good if you can be a bit elaborate. 
  

Nitish B.  
Well "T super String" => "Object". "Object" is extended by many which aren't String. So if I create a class A<T super String>{ T test; public void set(T a) {test = a}} and if its valid, I can create an instance A<Object> a = new A<Object>() and then do a.set((Some other class) object instance), what do you think will happen? 
  

Koustav C.  
Hey I got it.Thanks a lot. 

所以下面的這段代碼帶編譯通不過!!!!!!!!!!

<del><span style="color:#ff0000;">class Info<T super String>{</span></del>
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo15{
    public static void main(String args[]){
        Info<String> i1 = new Info<String>();
        i1.setVar("forfan06");
        System.out.prinln(i1.getVar());
    }
}

10.5 泛型與子類繼承的限制

             一個類的子類可以通過對象多態性爲其父類實例化,但是在泛型操作中,子類的泛型類型是無法使用父類的泛型類型接收的。 例如, Info<String>不能使用Info<Object>接收!!!!!!!

class Info<T>{
    private T var;
    public T getVar(){
        return var;
    }
    public void setVar(T var){
        this.var = var;
    }
    public String toString(){
        return this.var.toString();
    }
}
public class GenericsDemo16{
    public static void main(String args[]){
        Info<String> i1 = new Info<String>();      //泛型類型爲String
        Info<Object> i2 = null;                    //泛型類型爲Object
        i2 = i1;                //兩個Info對象進行轉換,Info<String>  -->  Info<Object>
    }
}

編譯時出錯:

GenericsDemo16.java:17: error: incompatible types
        i2 = i1;                //兩個Info對象進行轉換,Info  -->  Info
             ^
  required: Info
  found:    Info
1 error

編譯錯誤

以上錯誤表示: 不匹配的類型,即Info<String>無法轉換爲Info<Object>。 雖然String是Object類的子類。但是在泛型操作中此概念無效。此時只能使用通配符“?”接收·!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章