Java 泛型程式設計

語言: CN / TW / HK

1.  泛型類

public class Pair<T> 
{
   private T first;
   private T second;
   
   public void setSecond(T second){...}
   ....
 }

2.  泛型方法

class ArrAlg{
    public static <T> T getMiddle(T... a){
        return a[a.length/2];
    }
}

3.   型別變數的限定

public static <T extends BoundingType1 & BoundingType2 ...> T min(T[] a)....

這表示T應該是BoundingType型別的子型別。T和繫結型別可以是類,也可以是介面 。一個型別變數或萬用字元可以有多個限定,但限定中之多隻有一個類,這是因為單繼承,當有多個限定時,基類要寫在第一個,介面寫在後面。

4.  原始型別,任何一個泛型型別,都自動提供一個原始型別,原始型別的名字就是刪去型別引數後的型別名。例如Pair<T>, 原始型別就是Pair。

5.  泛型轉換

  •   虛擬機器中沒有泛型,只有普通的類和方法
  •        所有的型別引數都用它們的第一個限定型別替換,沒有寫明限定型別就用Object
  •        合成橋方法保持多型。
  •        為保持型別安全性,必要時插入強制型別轉換(例如呼叫泛型方法返回值)

5.  橋方法

假設有下列類繼承了Pair<Date>,

class DateInterval extends Pair<Date>
{
    public void setSecond(Date second){
    ....
   
    }
 }

實際擦除型別之後

class DateInterval extends Pair
{
    public void setSecond(Date second){...}
    
    //因為型別擦除,實際還存在一個繼承於Pair類的方法,顯然這是兩個方法
    public void setSecond(Object second){
         //實際生成橋方法,這裡會呼叫setSecond(Date seconde);即:
         setSecond((Date)second);
     }
 }

當如下呼叫時 Pair<Date> pair = new DateInterval(...);  pair.setSecond(aDate); 。Pair型別只有一個方法setSecond(Object)。實際引用DateInterval類,因而將會呼叫DateInterval.setSecond(Object)。編譯器為了呼叫最合適的方法,實際上生成了一個橋方法,如下  public void setSecond(Object second){  setSecond((Date) second)  }  。

6.  約束與侷限性

  • 不能用基本型別例項化型別引數
  • 執行時型別查詢只適用於原始型別
  • 不能建立引數化型別的陣列,即不能  new Pair<String> [10]
  • 不能例項化型別變數,即不能使用 new T(...), new T[] 或T.class這類的表示式。可以通過反射例項化T但不能T.class.newInstance(); 可以如下設計API來實現。
public static <T> Pair<T> makePair(Class<T> cl){
    try{
        return new Pair<>(cl.newInstance(), cl.newInstance())
        }catch(Exception e){return null;}
}
  • 泛型類的靜態上下文中不能引用型別變數,例如  private static T aData; 或者 public static T fun(){}都是錯誤的。
  • 不能丟擲或捕獲泛型類的例項

7.  萬用字元

  • 萬用字元限定: extends 、super

例:Pair<? extends Person>表示泛型Pair型別,它的引數是Person的子類。

在這種情況下,getter和setter區別,getter可以正常呼叫,但是setter不行,會產生編譯錯誤,因為編譯器不能確定要傳入引數的型別,也沒法代替。

?extends Person getFirst()
void setFirst(? extends Person)

反之,萬用字元的超型別限定: ?super Student

void setFirst(? super Student);
? super Student getFirst();

編譯器雖然不知道setFirst的確切型別,但是可以用任意Student物件呼叫,而不能用Person物件呼叫。如果呼叫getFirst,返回的物件不能保證,只能賦給Object。

  • 無限定萬用字元 Pair<?>
? getFirst();
void setFirst(?);

getFirst的返回值只能賦給一個Object, setFirst不能呼叫除非setFirst(null),對於一些簡單操作非常有用,例如判定是否為null getFirst() == null

  • 萬用字元捕獲   可以通過泛型方法捕獲萬用字元