標籤:nts 核心 sse 不同 基本 super 好的 erro 原始類型
泛型程式設計
泛型程式設計:編寫的代碼可以被很多不同類型的對象所重用。
型別參數:使用<String>,後者可以省略,因為可以從變數的類型推斷得出。型別參數讓程式更具更好的可讀性和安全性。
萬用字元類型:很抽象,讓庫的構建者編寫出儘可能靈活的方法。
定義簡單泛型類
泛型類就是具有一個或多個類型變數的類。
//參考型別變數 T ,可以有多個類型變數,public class Pair<T, U>{...}public class Pair<T> { //類定義的類型變數制定方法的傳回型別以及域和局部變數的類型 private T first; private T second; public Pair(){ first = null; second = null; } public Pair(T first, T second){ this.first = first; this.second = second; } public T getFirst(){ return first; } public T getSecond(){ return second; } public void setFirst(T newValue){ first = newValue; } public void setSecond(T newValue){ second = newValue; }}
在執行個體化類時,加上具體的類型替換類型變數就可以執行個體化類型。
Pair<String> //用具體的類型替換類型變數就可以執行個體化泛型型別//此時,構造器和方法的類型都會賦予Pair<String>()Pair<String>(String, String)String getFirst()String getSecond()void setFirst(String)void setSecond(String)
多個類型變數只要這樣即可:
public class Pair<T, U>{...}
調用時的結構:
Pair pair = new Pair<String>();
泛型可以看作普通類的工廠。
泛型方法
定義帶有型別參數的簡單方法
class Arraylg{ public static <T> getMiddle(T... a){ return a[a.length / 2]; }}
調用泛型方法:
String middle = Arraylg.<String>getMiddle("John","Q","Public");
類型變數的限定
有時類或方法需要對變數進行約束實現某種功能,對類型變數 T 設定限定實現:
<T extends BoundingType>
多個限定
<T extends Comparable & Serializable>
此時,就可以讓類型 T 限制為實現了這些介面。
泛型代碼和虛擬機器
虛擬機器沒有泛型型別對象,所有對象都屬於普通類。
類型擦除
泛型型別,自動提供了一個相應的原始類型:刪去型別參數後的泛型型別名。擦除類型變數,並替換為限定類型(無限定名的變數用 Object)。
經過編譯器之後,泛型在虛擬機器不存在。
翻譯泛型運算式
Pair<Employee> buddies = ...;Employee buddy = buddies.getFirst();
編譯器吧這個方法調用翻譯為兩條虛擬機器指令:
- 對原始方法 Pair.getFirst 的調用。
- 將返回的 Object 類型強制裝換為 Employee 類型。
翻譯泛型方法
public static <T extends Comparable> T min(T[] a)public static Comparable min(Comparable[] a)
型別參數 T 擦除,留下限定類型 Compatable 。
類型擦除與多態發生衝突,要在類中產生一個橋方法:
public void setSecong(Object second) { setSecond((Date) second); }
java 泛型轉換的事實:
- 虛擬機器中沒有泛型,只有普通的類和方法。
- 所有的型別參數都用它們的限定類型替換。
- 橋方法被合成來保持多態。
- 為保持型別安全,必要時插入強制類型轉換。
約束與局限性不能用基本類型執行個體化型別參數
可以用封裝器類型,如果封裝器類型不能接受替換時,可以使用獨立的類和方法處理他們
運行時類型查詢只適合於原始類型
無論何時對泛型使用 instanceof 會得到一個編譯器錯誤;
泛型型別的強制轉換運算式都會看到一個編譯警告。
getClass 方法總是返回原始類型。
Pair<String> stringPair = ...;Pair<Employee> employeePair = ...;stringPair.getClass() == employeePair.getClass();if (a instanceof Pair<String>) //ErrorPair<String> p = (Pair<String>) a; //Warning
結果為 true 都將返回 Pair.class
不能建立參數化型別的數組
Pair<String>[] table = new Pair<String>[10];//error
可以聲明萬用字元類型的數組,然後進行類型轉換:
Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];
但是結果不安全
安全方法:
ArrayList<Pair<String>>
Varargs 警告
向參數個數可變的方法傳遞一個泛型型別的執行個體:
public static <T> void addAll(Collection<T> coll, T... ts){ for (t : ts) coll.add(t);}
此時需要 ts 數組,但是他違反了規則,會得到一個警告,可以通過注釋消除警告:
@SafeVarargspublic static <T> void addAll(Collection<T> coll, T... ts)
此時可以提供泛型型別來調用這個方法了。
不能執行個體化類型變數
非法:public Pair() { first = new T(); second = new T(); } // Error
同樣反射調用也非法:fisrt = T.class.newInstance(); // Error
不能構造泛型數組
非法:T[] mm = new T[2];
泛型類的靜態上下文中類型變數無效
不能在靜態域或方法中參考型別變數。
private static T s; // Errorpublic static T getS(){...}; // Error
不能拋出或捕獲泛型類的執行個體
非法:public class Problem<T> extends Exception { /*...*/ } // Error
catch 子句中不能使用類型變數。
在異常規範中使用類型變數是允許的:public static <T extends Throwable> void doWork(T t) throws T // OK
可以消除對受檢查異常的檢查
異常處理規定,必須為所有受查異常提供一個處理器。不過可以利用泛型消除這個限制。
注意擦除後的衝突
當泛型型別被擦除時,無法建立引發衝突的條件。
要想支援擦除的轉換,就需要強行限制一個類或類型變數不能同時成為兩個介面類型的子類,而這兩個介面是同一介面的不同參數化。
非法:
class Employee implements Comparable { ... }class Manager extends Employee implements Comparable<Manager> { ... } //Error
Manager會實現Comparable<Employee>和Comparable<Manager>,這是同一介面的不同參數化。
泛型型別的繼承規則
Pair<Manager> 不是 Pair<Employee> 的子類。可以將參數類型轉換為一個原始類型,但是會產生類型錯誤。
無論 S 與 T 有什麼關係,Pair<Manager> 和 Pair<Employee>基本沒什麼關係。
萬用字元類型萬用字元概念
萬用字元類型中,允許型別參數變化。
Pair<? extends Employee>
便是任意泛型 Pair 類型,它的型別參數是 Employee 子類。
Pair<Manager> 和 Pair<Employee>是Pair<? extends Employee>的子類型。
萬用字元的超類型限定
超類型限定:
? super Manager
這個萬用字元限制為 Manager 的所有超類型。
public static <T extends Comparable<T>> T min(T[] a)public static <T extends Comparable<? super T>> T min(T[] a) //用於處理需要泛型型別超類型的情況
無限定通配合
無限定萬用字元:Pair<?>
這個類有如下方法:
? getFirst()void setFirst(?)
getFirst 能賦予一個 Object;
setFirst 方法不能被調用,也不能用 Object 調用。但是可以調用setFirst(null)。
萬用字元捕獲
萬用字元不是類型變數,不能在編寫代碼中使用?作為一種類型。
解決方案:
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);}
swapHelper 方法的參數 T 捕獲萬用字元。他不知道是哪種類型的萬用字元,但是這是一個明確的類型。
反射和泛型泛型 Class 類
Class 類是泛型的,String.class是一個Class<String>類的對象(唯一的對象)。
如果給定的類型確實是 T 的一個子類型,cast 方法就會返回一個現在聲明為類型 T 的對象,否則,拋出一個 BadCastException 異常。
如果這個類不是 enum 類或類型 T 的枚舉值的數組,getEnumConstants 方法將返回 null。
getConstructor 與 getdeclaredConstructor 方法返回一個 Constructor<T>對象。Constructor類也已經變成泛型,以便newInstance方法有一個正確的傳回型別。
API:
T newInstance()T cast(Object obj)T[] getEnumConstants()Class<? super T> getSuperclass()Constructor<T> getConstructor<Class... parameterTypes)Constructor<T> getDeclaredConstructor<Class... parameterTypes)//java.lang.reflect.Constructor<T>T newInstance(Object... parameters)
使用 Class<T> 參數進行類型匹配
public static <T> Pair<T> makePair(class<T> c) throws InstantiationException, IllegalAccessException { return new Pair<>(c.newInstance(), c.newInstance());}
調用
makePair(Employee.class);
型別參數 T 同 Employee 匹配。
虛擬機器中的泛型型別資訊
可以使用反射 API 確定:
- 這個泛型方法有一個叫做 T 的型別參數。
- 這個型別參數有一個子類限定,其自身又是一個泛型型別。
- 這個限定類型有一個萬用字元參數。
- 這個萬用字元參數有一個超類限定。
- 這個泛型方法有一個泛型數組參數。
Java核心技術卷一 6. java泛型程式設計