-
(P 327)“菱形”語法:
ArrayList<String> files = new ArrayList<>(); // Java 9 擴充套件了菱形語法的使用範圍,例如:現在可以對匿名子類使用菱形語法 ArrayList<String> passwords = new ArrayList<>() { public String get(int n) { return super.get(n).replaceAll(".", "*"); } }
-
(P 328)定義泛型類:
public class Pair<T, U> { ... }
常見的做法是型別變數使用大寫字母,而且很簡短:
E
表示集合的元素型別K
、V
分別表示表的鍵和值的型別T
、U
、S
表示任意型別
-
(P 330)定義泛型方法:型別變數放在修飾符的後面,並在返回型別的前面
class ArrayAlg { public static <T> T getMiddle(T... a) { ... } }
呼叫泛型方法:
String middle = ArrayAlg.<String>getMiddle("John", "Q.", "Public"); // 大多數情況下,可以省略型別引數 String middle = ArrayAlg.getMiddle("John", "Q.", "Public"); // 編譯器將引數的型別與泛型型別T進行匹配,推斷出T一定是String
-
(P 332)型別變數的限定
T
是限定型別(bounding type)的子型別:<T extends BoundingType>
一個型別變數或萬用字元可以有多個限定,限定型別用“&”分隔,而逗號用來分隔型別變數
<T extends BoundingType1 & BoundingType2>
可以擁有多個介面超型別,但最多有一個限定可以是類。如果有一個類作為限定,它必須是限定列表中的第一個限定
-
(P 333)型別擦除:無論何時定義一個泛型型別,都會自動提供一個相應的原始型別。這個原始型別的名字就是去掉型別引數後的泛型型別名。型別變數會被擦除,並替換為其限定型別(或者,對於無限定的變數則替換為Object)
-
(P 334)為了提高效率,應該將標籤介面(即沒有方法的介面)放在限定列表的末尾
-
(P 335)呼叫一個泛型方法時,編譯器會擦除返回型別,並插入強制型別轉換。當訪問一個泛型欄位時,也會插入強制型別轉換。
Pair<Employee> buddies = ...; Employee buddy = buddies.getFirst(); // 編譯器會做如下類似的處理 Pair buddies = ...; // 擦除型別引數,Pair中的所有泛型被替換為Object Employee buddy = (Employee) buddies.getFirst(); // 插入強制型別轉換(方法原來的返回型別被擦除變成了Object)
-
(P 335)橋方法:用來解決多型呼叫和型別擦除的衝突
方法的擦除會帶來兩個問題,考慮如下程式碼:
class DateInterval extends Pair<LocalDate> { // (偽)重寫父類中的方法 // 之所以這裡加個“偽”字,是因為父類的型別引數會被編譯器擦除,變成Object,所以這裡實際上是過載了父類中的方法,只是看起來像重寫 public void setSecond(LocalDate second) { ... } // 這個類中,除了上面的那個外,還存在一個從父類繼承的方法 public void setSecond(Object second); }
這樣在多型呼叫時會產生問題:
DateInterval interval = ...; Pair<LocalDate> pair = interval; pair.setSecond(aDate); // 這裡呼叫的是哪個方法呢?型別擦除和多型發生了衝突 // 如果編譯器什麼都不做,將呼叫Pair.setSecond(Object),因為Pair中只存在這一個setSecond方法 // 而我們希望進行多型呼叫,即呼叫DateInterval.setSecond(LocalDate)
為了解決這個問題,編譯器會在子類中生成一個橋方法:
class DateInterval extends Pair<LocalDate> { // (偽)重寫父類中的方法 public void setSecond(LocalDate second) { ... } // 編譯器生成的橋方法,重寫了父類的setSecond方法 public void setSecond(Object second) { setSecond((LocalDate) second); // 呼叫上面的那個setSecond方法 } }
另外,還有一個問題,考慮如下程式碼:
class DateInterval extends Pair<LocalDate> { // (偽)重寫父類中的方法 public LocalDate getSecond() { ... } // 同理,編譯器會生成橋方法,以便進行多型呼叫 public Object getSecond() { return (LocalDate) getSecond(); // 這裡呼叫的是哪個方法呢?方法過載時要求引數型別不同,但是這裡兩個getSecond方法都沒有引數,似乎不合法 } }
程式設計師是不能這樣編寫Java程式碼的,但是在虛擬機器中,會由引數型別和返回型別共同指定一個方法。因此,編譯器可以為兩個僅返回型別不同的方法生成位元組碼,虛擬機器能夠正確地處理這種情況
-
(P 337)對於Java泛型的轉換,有如下幾個事實:
- 虛擬機器中沒有泛型,只有普通的類和方法
- 所有的型別引數都會替換為它們的限定型別
- 會合成橋方法來保持多型
- 為保持型別安全性,必要時會插入強制型別轉換
-
(P 337)在泛型程式碼和遺留程式碼之間進行互操作時,編譯器會發出一個警告,可以通過加註解
@SuppressWarnings("unchecked")
使之消失// 將泛型物件賦給原始型別物件 Dictionary<Integer, Component> labelTable = ...; @SuppressWarnings("unchecked") // 抑制編譯器的警告 slider.setLabelTabel(labelTable); // warning // 將原始型別物件賦給泛型物件 @SuppressWarnings("unchecked") // 抑制編譯器的警告 Dictionary<Integer, Component> labelTable = slider.getLabelTable(); // warning
-
(P 338)限制與侷限性:
-
不能用基本型別例項化型別引數
Pair<double> pair = ...; // 不合法,double是基本型別
-
執行時型別查詢只適用於原始型別
if (a instanceof Pair<String>) // 錯誤 if (a instanceof Pair<T>) // 錯誤 if (a instanceof Pair) // 正確 Pair<String> pair = (Pair<String>) a; // 錯誤
getClass
方法總是返回原始型別Pair<String> stringPair = ...; Pair<Employee> employeePair = ...; if (stringPair.getClass() == employeePair.getClass()) // 比較結果為true,兩個getClass呼叫都返回Pair.class
-
不能建立引數化型別的陣列(可以宣告,但不能建立)
var table = new Pair<String>[10]; // 錯誤 var table = (Pair<String>[]) new Pair<?>[10]; // 可以,但是結果將是不安全的
如果需要收集引數化型別物件,簡單地使用
ArrayList
更安全、有效var table = new ArrayList<Pair<String>>(); // 合法
-
Varargs警告:向引數個數可變的方法傳遞一個泛型型別的例項,編譯器會發出一個警告,可以使用
@SuppressWarnings("unchecked")
或者@SafeVarargs
註解來抑制這個警告@SafeVarargs public static <T> void addAll(Collection<T> coll, T... ts) // 呼叫這個方法時,虛擬機器必須要建立T型別的陣列ts // 這違反了前面的規則,但此時編譯器只會發出一個警告
- 對於任何只需要讀取引數陣列元素的方法,都可以使用
@SafeVarargs
註解 @SafeVarargs
只能用於宣告為static
、final
或private
的構造器和方法。
- 對於任何只需要讀取引數陣列元素的方法,都可以使用
-
不能例項化型別變數
public Pair() { first = new T(); // 錯誤 second = new T(); // 錯誤 }
Java 8之後,最好的解決辦法:讓呼叫者提供一個構造器表示式
public static <T> Pair<T> makePair(Supplier<T> constr) { return new Pair<>(constr.get(), constr.get()); } Pair<String> p = Pair.makePair(String::new);
傳統的解決方法:通過反射呼叫
Constructor.newInstance
方法來構造泛型物件first = T.class.getConstructor().newInstance(); // 錯誤,T被擦除為Object public static <T> Pair<T> makePair(Class<T> cl) { try { return new Pair<>(cl.getConstructor().newInstance(), cl.getConstructor().newInstance()); } catch (Exception e) { return null; } } Pair<String> p = Pair.makePair(String.class);
-
不能構造泛型陣列
public static <T extends Comparable> T[] minmax(T... a) { T[] mm = new T[2]; // 錯誤 ... }
-
泛型類的靜態上下文中型別變數無效:不能在靜態欄位或方法中引用型別變數
public class Singleton<T> { private static T singleInstance; // 錯誤 public static T getSingleInstance() { // 錯誤 ... } }
-
不能丟擲或捕獲泛型類的例項
public class Problem<T> extends Exception { ... } // 錯誤,泛型類不能擴充套件Throwable try { ... } catch (T e) { ... } // 錯誤,catch子句中不能使用型別變數
-
可以取消對檢查型異常的檢查
通過使用泛型類、擦除和
@SuppressWarnings
註解,我們就能消除Java型別系統的部分基本限制(詳見P 343 ~ P 345) -
注意擦除後的衝突:例如在類中增加一個
equals
方法就可能和從Object
中繼承的equals
方法衝突倘若兩個介面型別是同一介面的不同引數化,一個類或型別變數就不能同時作為這兩個介面型別的子類
class Employee implements Comparable { ... } class Manager extends Employee implements Comparable { ... } // 錯誤
-
-
(P 346)具有繼承關係的類如果作為泛型類的型別引數,則這些泛型類之間沒有繼承關係(萬用字元型別可以解決這個問題),例如
Employee
和Manager
具有繼承關係,但是Pair<Employee>
和Pair<Manager>
之間沒有繼承關係。注意:陣列型別Employee[]
和Manager[]
之間具有繼承關係 -
(P 347)總是可以將引數化型別轉換為一個原始型別
var managerBuddies = new Pair<Manager>(...); Pair rawBuddies = managerBuddies; // 合法
-
(P 347)泛型類可以擴充套件或實現其他的泛型類。如:
ArrayList<T>
實現了List<T>
介面,這意味著ArrayList<Manager>
實現了List<Manager>
介面 -
(P 348)萬用字元:在萬用字元型別中,允許型別引數發生變化
Pair<? extends Employee> // 表示任何泛型Pair型別,它的型別引數是Employee的子類 // 如Pair<Manager>是Pair<? extends Employee>的子類
其中的方法如下:
? extends Employee getFirst() // 合法,可以將返回值賦給一個Employee void setFirst(? extends Employee) // 這樣不可能呼叫這個方法,它拒絕傳遞任何特定的型別
-
(P 349)超型別限定:
? super Manager
,這個萬用字元限制為Manager的所有超型別void setFirst(? super Manager) // 合法,可以向方法傳遞一個Manager物件,或者其子型別的物件 ? super Manager getFirst() // 不能呼叫這個方法,它無法確定返回值的型別,只能賦給Object
-
(P 350)直觀地講,帶有超型別限定的萬用字元允許你寫入一個泛型物件,而帶有子型別限定的萬用字元允許你讀取一個泛型物件
-
(P 351)無限定萬用字元:在編寫不需要實際型別的方法時很有用,可讀性更好
? getFirst() // 返回值只能賦給Object void setFirst(?) // 不能被呼叫,甚至不能傳遞Object(原始的Pair型別可以,這是Pair<T>和Pair主要的不同),可以傳遞null
-
(P 352)不能在編寫程式碼中使用“
?
”作為一種型別,必須儲存?
型別的變數時,可以通過編寫輔助方法(泛型方法)解決 -
(P 353)萬用字元捕獲只有在非常限定的情況下才是合法的,編譯器必須能夠保證萬用字元表示單個確定的型別
-
(P 356)可以使用
java.lang.reflect
包中的介面Type
表述泛型型別的宣告,其包含以下子類:Class
類,描述具體型別TypeVariable
介面,描述型別變數WildcardType
介面,描述萬用字元ParameterizedType
介面,描述泛型類或介面型別GenericArrayType
介面,描述泛型陣列
《Java核心技術(卷1)》筆記:第8章 泛型程式設計
相關文章
- Java核心技術第八章——泛型程式設計(1)Java泛型程式設計
- Java核心技術卷閱讀隨筆--第3章【Java 的基本程式設計結構】Java程式設計
- 《Java核心技術(卷1)》筆記:第7章 異常、斷言和日誌Java筆記
- Java核心技術 卷1 基礎知識 部分筆記Java筆記
- java筆記-two-java泛型程式設計(簡記)Java筆記泛型程式設計
- java 泛型程式設計Java泛型程式設計
- 《Java核心技術 卷I》學習筆記2:資料型別、變數與常量Java筆記資料型別變數
- Java泛型筆記Java泛型筆記
- 《Java 多執行緒程式設計核心技術》筆記——第3章 執行緒間通訊(三)Java執行緒程式設計筆記
- 《Java 多執行緒程式設計核心技術》筆記——第3章 執行緒間通訊(四)Java執行緒程式設計筆記
- Java核心技術筆記 繼承Java筆記繼承
- java核心技術閱讀筆記Java筆記
- 《Java核心技術 卷I》學習筆記10:使用預定義類Java筆記
- java核心技術卷1 第五章:繼承Java繼承
- java核心技術卷1學習思維導圖Java
- java核心技術筆記--執行緒Java筆記執行緒
- JAVA核心技術學習筆記--反射Java筆記反射
- 泛型程式設計泛型程式設計
- JavaScript DOM程式設計藝術筆記1JavaScript程式設計筆記
- Java高階程式設計筆記 • 【第4章 網路程式設計】Java程式設計筆記
- freeRTOS核心學習筆記(1)-程式設計標準筆記程式設計
- 02. 程式設計核心內功心法之泛型程式設計泛型
- Java程式設計思想學習筆記4 - 序列化技術Java程式設計筆記
- 《Java程式設計思想》筆記8.多型Java程式設計筆記多型
- 《Windows核心程式設計》筆記(一)Windows程式設計筆記
- C++核心程式設計筆記C++程式設計筆記
- Java核心技術總結一:Java的基本程式設計結構Java程式設計
- Java核心知識1:泛型機制詳解Java泛型
- Java 核心技術卷 I (第 10 版,基於 java8) 第一二章總結Java
- 十、GO程式設計模式 : 泛型程式設計Go程式設計設計模式泛型
- Java核心技術筆記 異常、斷言和日誌Java筆記
- JavaScript DOM 程式設計藝術(第2版) 讀書筆記JavaScript程式設計筆記
- Java核心之細說泛型Java泛型
- Java核心技術學習筆記——進階——第六章 Java網路程式設計——6.1 網路基礎知識Java筆記程式設計
- 《大型網站技術架構:核心原理與案例分析》讀書筆記 - 第1篇 概述網站架構筆記
- Java程式設計思想學習筆記1 - 內部類Java程式設計筆記
- 泛型程式設計詳解(一)泛型程式設計
- 泛型程式設計與 OI——modint泛型程式設計