Java核心技術卷一 6. java泛型程序設計
泛型程序設計
泛型程序設計:編寫的代碼可以被很多不同類型的對象所重用。
類型參數:使用<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>) //Error
Pair<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 數組,但是他違反了規則,會得到一個警告,可以通過註釋消除警告:
@SafeVarargs
public 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; // Error
public 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泛型程序設計