1. 程式人生 > >java 核心技術卷I學習記錄(八)- 泛型程式設計

java 核心技術卷I學習記錄(八)- 泛型程式設計

java 核心技術卷第八章:泛型程式設計

##1. 為什麼要使用泛型程式設計
泛型程式設計(Generic programming) 意味著編寫的程式碼可以被很多不同型別的物件所重用。

2. 定義簡單泛型類

public class Pair<T>
{
    private T first;
    private T second;
    public Pair() { first = null ; second = null ; }
    public PairfT first , T second) { this,first = first; this.second = second; }
    public T getFirstO { return first; }
    public T getSecondO { return second; }
    public void setFirst (T newValue) { first = newValue; }
    public void setSecond(T newValue) { second = newValue; }
}

3. 泛型方法

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

String middle = ArrayAlg.<String>getMiddle("]ohnM, "Q.n, "Public");

4. 型別變數的限定

public static <T extends Coiparab1e> T min(T[] a) . . .

下面的記法<T extends BoundingType〉表示T 應該是繫結型別的子型別(subtype)。T 和繫結型別可以是類, 也可以是介面。選擇關鍵字extends 的原因是更接近子類的概念, 並且Java 的設計者也不打算在語言中再新增一個新的關鍵字(如sub)。一個型別變數或萬用字元可以有多個限定,如:T extends Comparable & Serializable

5. 泛型程式碼和虛擬機器

無論何時定義一個泛型型別, 都自動提供了一個相應的原始型別( raw type )。原始型別的名字就是刪去型別引數後的泛型型別名。擦除( erased) 型別變M, 並替換為限定型別(無限定的變數用Object)。如Pair 原始型別,因為T 是一個無限定的變數, 所以直接用Object 替換。

public class Pair
{
private Object first;
private Object second;
public Pair(Object first, Object second)
{
	this,first = first;
	this.second = second;
}
public Object getFirstO { return first; }
public Object getSecondO { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
}

原始型別用第一個限定的型別變數來替換, 如果沒有給定限定就用Object 替換。

  • 當程式呼叫泛型方法時, 如果擦除返回型別, 編譯器插入強制型別轉換。
Pair<Employee> buddies = . .
Employee buddy = buddies.getFirst();
擦除getFirst 的返回型別後將返回Object 型別。編譯器自動插人Employee 的強制型別轉換。

需要記住有關Java 泛型轉換的事實:

  • 虛擬機器中沒有泛型, 只有普通的類和方法。
  • 所有的型別引數都用它們的限定型別替換。
  • 橋方法被合成來保持多型。
  • 為保持型別安全性,必要時插人強制型別轉換。

6. 約束與侷限性

  1. 不能用基本型別例項化型別引數

不能用型別引數代替基本型別。因此, 沒有Pair, 只有Pair。當然,其原因是型別擦除。擦除之後, Pair 類含有Object 型別的域, 而Object 不能儲存double 值。

  1. 執行時型別查詢 只適用於原始型別
if (a instanceof Pair<String>) // Error
if (a instanceof Pair<T>) // Error
試圖查詢一個物件是否屬於某個泛型型別時, 倘若使用instanceof 會得到一個編譯器錯誤, 如果使用強制型別轉換會得到一個警告。

Pair <String> stringPair = . .
Pair < Employee〉employeePair = . .
if (stringPair.getClassO == employeePair .getClassO) // they are equal 這是因為兩次呼叫getClass 都將返回Pair.class。
  1. 不能建立引數化型別的陣列
Pair<String>[] table = new Pair<String>[10] ; // Error

可以通過型別ArrayList來克服這一個困難

  1. Varargs 警告

    向引數個數可變的方法傳遞一個泛型型別的例項。考慮下面這個簡單的方法, 它的引數個數是可變的:

    public static <T> void addAll(Collections coll, T... ts)
    {
    	for (t : ts) coll.add⑴;
    }
    

為了呼叫這個方法,Java 虛擬機器必須建立一個Pair 陣列, 這就違反了前面的規則。不過,對於這種情況, 規則有所放鬆, 你只會得到一個警告,而不是錯誤。可以採用兩種方法來抑制這個警告。一種方法是為包含addAll 呼叫的方法增加註解@SuppressWamings(“unchecked”)。 或者在Java SE 7 中, 還可以用SafeVarargs 直接標註addAll 方法:

@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts)
  1. 不能例項化型別變數

不能使用像new T(…),newT[…] 或T.class 這樣的表示式中的型別變數

  1. 不能構造泛型陣列

  2. 泛型類的靜態上下文中型別變數無效

  3. 不能丟擲或捕獲泛型類的例項

  4. 注意擦除後的衝突

catch (T e) // Error can't catch type variable

7. 泛型型別的繼承規則

泛型類可以擴充套件或實現其他的泛型類。

8. 萬用字元型別

  • 萬用字元型別中, 允許型別引數變化。例如, 萬用字元型別
    Pair<? extends Employee>
public static void printBuddies(Pair <Employee> p)
{
    Employee first = p.getFirst();
    Employee second = p.getSecondO;
    Systefn.out.println(first.getName() + " and " + second.getNameQ + " are buddies.");
}

但是不能將Pair傳遞給這個方法!但是當方法改為public static void printBuddies(Pair<? extends Eiployee> p)但是在使用wi1dcardBuddies.setFirst(1owlyEnployee) ; // compile-time error會出現編譯時錯誤。這樣將不可能呼叫setFirst 方法。編譯器只知道需要某個Employee 的子型別, 但不知道具體是什麼型別。它拒絕傳遞任何特定的型別。

  • 萬用字元的超型別限定

Pair <? super Manager>

只能傳遞Manager 型別的物件, 或者某個子型別(如Executive) 物件。另外, 如果呼叫getFirst , 不能保證返回物件的型別。只能把它賦給一個Object。

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

Pair<?> 和Pair 本質的不同在於: 可以用任意Object 物件呼叫原始Pair 類的setObject方法。

  • 萬用字元捕獲

編寫一個交換成對元素的方法:
public static void swap(Pair<?> p)

萬用字元不是型別變數, 因此, 不能在編寫程式碼中使用“ ?” 作為一種型別。但是可以通過swaphelper來進行操作。

public static <T> void swapHelper(Pair<T> p)
{
    T t = p.getFirst();
    p.setFirst(p.getSecond());
    p.setSecond(t);
}


public static void swap(Pair<?> p) { swapHelper(p) ; }

9. 反射和泛型

Java 泛型的卓越特性之一是在虛擬機器中泛型型別的擦除。

可以通過反射的方式來獲取泛型中的相關型別等必要資訊,具體方法可參考反射的具體api