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