《Java 核心技术卷1 第10版》学习笔记 ------ 泛型【进阶】
這部分主要是結合 Java 虛擬機實現泛型的原理進一步研究如何更好的使用泛型。
8.5 泛型代碼和虛擬機
虛擬機沒有泛型類型對象---所有對象都屬于普通類。所以編譯器在編譯的時候會進行類型擦除操作。
8.5.1 類型擦除
1. 什么是類型擦除?
無論何時定義一個泛型類型, 都自動提供了一個相應的原始類型 ( raw type )。原始類型的名字就是刪去類型參數后的泛型類型名。擦除( erased) 類型變量, 并替換為限定類型(無限定的變量用 Object。)
擦除規則:原始類型用第一個限定的類型變量來替換, 如果沒有給定限定就用 Object 替換。
例如 Pair<T> 的原始類型如下:
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; } }// 因為 T 是無限定的變量,所以用 Object 代替結果是一個普通的類, 就好像泛型引人 Java 語言之前已經實現的那樣。
多個限定類型例子:
// 泛型類【多個限定修飾符 Comparable 和 Serializable】 public class Interval <T extends Comparable & Serializable〉implements Serializable {private T lower;private T upper;public Interval (T first, T second){if (first.compareTo(second) <= 0) { lower = first; upper = second; }else { lower = second; upper = first; }} }// 類型擦除后【多個限定符,選擇第一個限定符 Comparable 】 public class Interval implements Serializable {private Comparable lower;private Comparable upper;public Interval (Coiparable first, Coiparable second) { . . . } }注釋:讀者可能想要知道切換限定: class Interval<T extends Serializable & Comparable>會發生什么。如果這樣做, 原始類型用 Serializable 替換 T, 而編譯器在必要時要向Comparable 插入強制類型轉換。為了提高效率,應該將標簽(tagging) 接口(即沒有方法的接口)放在邊界列表的末尾。
8.5.2 翻譯泛型表達式
當程序調用泛型方法時,如果擦除返回類型, 編譯器插入強制類型轉換。例如,下面這個語句序列
Pair<Employee> buddies = ...; Employee buddy = buddies.getFirst();擦除 getFirst 的返回類型后將返回 Object 類型。編譯器自動插人 Employee 的強制類型轉換。
也就是說,編譯器把這個方法調用翻譯為兩條虛擬機指令:
- 對原始方法 Pair.getFirst 的調用。
- 將返回的 Object 類型強制轉換為 Employee 類型。
8.5.3 翻譯泛型方法
類型擦除也會出現在泛型方法中。程序員通常認為下述的泛型方法
public static <T extends Comparable〉T min(T[] a)是一個完整的方法族,而擦除類型之后,只剩下一個方法:
// 擦除泛型之后 public static Comparable min(Comparable[] a)注意,類型參數 T 已經被擦除了, 只留下了限定類型 Comparable。
方法的擦除帶來了兩個復雜問題。看一看下面這個示例:
class DateInterval extends Pair<LocalDate>{public void setSecond(LocalDate second){if(second.compareTo( getFirst() ) >= 0){super.setSecond(second);}} }上面代碼的意思是,一個日期區間是一對 LocalDate 對象,并且需要覆蓋這個方法來確保第二個值永遠不小于第一個值。這個類擦除后變成:
class DataInterval extends Pair // after erasure {public void setSecond(LocalDate second){...} }令人感到奇怪的是,存在另一個從 Pair 繼承的 setSecond 方法,即
// Pair 中存在的 setSecond 方法 public void setSecond(Object second)這顯然是一個不同的方法,因為它有一個不同類型的參數 Object, 而不是 LocalDate。然而,不應該不一樣。考慮下面的語句序列:
Datelnterval interval = new Datelnterval(. . .); Pair<Loca1Date> pair = interval; // OK assignment to superclass pair.setSecond(aDate);這里, 希望對 setSecond 的調用具有多態性, 并調用最合適的那個方法。由于 pair 引用 Datelnterval 對象,所以應該調用?Datelnterval.setSecond。問題在于類型擦除與多態發生了沖突。要解決這個問題, 就需要編譯器在 Datelnterval 類中生成一個橋方法(bridge method):
// 編譯器重寫了 Pair 的 setSecond(Object second) @Override public void setSecond(Object second) { setSecond( (LocalDate) second ); // 調用擦除后的 setSecond( (LocalDate) second)【橋方法】 }要想了解它的工作過程,請仔細地跟蹤下列語句的執行:
pair.setSecond(aDate);變量 pair 已經聲明為類型 Pair<LocalDate>, 并且這個類型只有一個簡單的方法叫 setSecond, 即 setSecond(Object second) 虛擬機用 pair 引用的對象調用這個方法。這個對象是 Datelnterval 類型的, 因而將會調用 Datelnterval.setSecond(Object)方法。這個方法是合成的
橋方法。它調用 Datelnterval.setSecond(LocalDate), 這正是我們所期望的操作效果。
橋方法可能會變得十分奇怪。假設 Datelnterval 方法也覆蓋了 getSecond 方法:
class Datelnterval extends Pair<LocalDate> {public LocalDate getSecond() { return (Date) super.getSecond().clone(); } }// 在 Datelnterval 類中,有兩個 getSecond 方法: LocalDate getSecond() // defined in Datelnterval Object getSecond() // overrides the method defined in Pair to call the first method不能這樣編寫 Java 代碼(在這里,具有相同參數類型的兩個方法是不合法的)。它們都沒有參數。但是,在虛擬機中,用參數類型和返回類型確定一個方法。因此, 編譯器可能產生兩個僅返回類型不同的方法字節碼,虛擬機能夠正確地處理這一情況。
總之,需要記住有關 Java 泛型轉換的事實:
- 虛擬機中沒有泛型,只有普通的類和方法。
- 所有的類型參數都用它們的限定類型替換。
- 橋方法被合成來保持多態。
- 為保持類型安全性,必要時插人強制類型轉換
8.5.4 調用遺留代碼
設計 Java 泛型類型時,主要目標是允許泛型代碼和遺留代碼之間能夠互操作。
8.6 約束與局限
限于時間及該部分實際情況下遇到這么細小的問題機會比較少,不詳細寫,更多參考書本。PS:其實只要理解了上面提到的類型擦除過程就很容易理解下面列出的這些約束。
1. 不能用基本數據類型實例化參數類,應使用基本類型對應的包裝類對象
?【錯誤:Pair<double>、Pair<int>;正確:Pair<Double>、Pair<Int>】
2. 運行時類型查詢只適合用于原始類型
? ?if(a instanceof pair<String>) // Error
? ?if(a instanceof pair<T>) // Error
3. 不能創建參數化類型數組
? ?Pair<String>[] table = new Pair<String>[10] //Error
4. varargs警告
5. 不能實例化類型變量
? ?public Pair() { first = new T(); second = new T(); }
6. 不能構造泛型數組
? ?public static <T extends Comparable〉T[] minmax(T[] a) { T[]?mm?= new T[2]; . . . } // Error
7. 泛型類的靜態上下文中類型變量無效
? ?private static T singlelnstance; // Error
? ?public static T getSinglelnstance(){ ... }?// Error
8. 不能拋出或者捕獲泛型類的實例
? ?public class Problem<T> extends Exception { /* . . . */ } // Error can't extend Throwable
9. 可以消除對受查異常的檢查
10. 注意擦除后的沖突
8.7 泛型類型的繼承規則
略,詳情看書
8.8 通配符類型
8.8.1. 通配符的概念(?)
通配符類型中, 允許類型參數變化。 例如, 通配符類型
Pair<? extends Employee>
表示任何泛型 Pair 類型, 它的類型參數是 Employee 的子類, 如 Pair<Manager>, 但不是 Pair<String>。
使用通配符會通過 Pair<? extends Employee> 的引用破壞 Pair<Manager> 嗎?
Pair<Manager> managerBuddies = new Pair<>(ceo, cfo); Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK wi1dcardBuddies.setFirst(lowlyEmployee); // compile-time error這可能不會引起破壞。對 setFirst 的調用有一個類型錯誤。要了解其中的緣由,請仔細看一看類型 Pair<? extends Employee>。其方法似乎是這樣的:
??? extends Employee getFirst()
? void setFirst(? extends Employee)
這樣將不可能調用 setFirst 方法。編譯器只知道需要某個 Employee 的子類型,但不知道具體是什么類型。它拒絕傳遞任何特定的類型。畢竟?不能用來匹配。
使用 getFirst 就不存在這個問題: 將 getFirst 的返回值賦給一個 Employee 的引用完全合法。這就是引人有限定的通配符的關鍵之處?,F在已經有辦法區分安全的訪問器方法和不安全的更改器方法了。
8.8.2. 通配符的超類限定(super 關鍵字)
通配符限定與類型變量限定十分類似,但是,還有一個附加的能力,即可以指定一個超類型限定 (supertypebound), 如下所亦:
? super Manager
這個通配符限制為 Manager 的所有超類型。帶有超類型限定的通配符的行為與 8.8.1 節介紹的相反。可以為 set 方法提供參數, 但不能使用get返回值。例如, Pair<? super Manager> 有方法
???????void setFirst(? super Manager) ? super Manager getFirst()這不是真正的 Java 語法,但是可以看出編譯器知道什么。編譯器無法知道 setFirst 方法的具體類型, 因此調用這個方法時不能接受類型為 Employee 或 Object 的參數。 只能傳遞 Manager 類型的對象,或者某個子類型(如 Executive?總經理, Manager 的子類) 對象。另外, 如果調用 getFirst, 不能保證返回對象的類型。只能把它賦給一個 Object。
直觀地講,帶有超類型限定的通配符可以向泛型對象寫人,帶有子類型限定的通配符可以從泛型對象讀取。
8.8.3. 無限定通配符
Pair<?>?初看起來,這好像與原始的 Pair 類型一樣。實際上, 有很大的不同。類型 Pair<?> 有以下方法:
? getFirst()
void setFirst(?)
getFirst 的返回值只能賦給一個 Object。setFirst 方法不能被調用, 甚至不能用 Object 調用。Pair<?> 和 Pair 本質的不同在于: 可以用任意 Object 對象調用原始 Pair 類的 setObject 方法。
8.8.4. 通配符捕獲
詳情參考書。
8.9 反射和泛型【待完善】
8.9.1 泛型 Class 類
?
8.9.2 使用 Class 參數進行類型匹配
8.9.3 虛擬機中的泛型類型信息
?
總結
以上是生活随笔為你收集整理的《Java 核心技术卷1 第10版》学习笔记 ------ 泛型【进阶】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SessionStorage 和 Loc
- 下一篇: bloom filter 的Java 版