泛型程序設計
Generic programming意味着代碼可以被很多不同類型的對象所重用
提供類型參數(type parameters)解決了繼承程序設計中的強制轉換的安全性、可讀性問題
基本概念
類
泛型類(generic class):具有一個或多個類型變量的類。
定義:public class Pair
類型變量指定方法的返回類型以及域和局部變量的類型
- E 集合的元素類型
- K、V 表的關鍵字與值的類型
- T/U/S 任意類型
用具體類型替換類型變量可以實例化泛型類型,泛型類可看作普通類的工廠
方法
定義:public static T getMiddle(T… a)
調用:ArrayAlg.getMiddle(可省略<類型參數>,編譯器會自行推斷,有時會提示錯誤)
類型限定
類或方法對類型變量加以約束
表示T是綁定類型的子類型
一個類型變量或通配符可以有多個類型限定:
T extends Comparable & Serializable
(限定中至多有一個類)
原理
類型擦除
對於虛擬機,編譯器提供相應的原始類型(刪去類型參數後的名字),erased perameter type,替換爲限定類型(無限定類型的變量用Object,多個限定的用首個,應將標籤接口放到限定後面)
翻譯泛型方法
方法擦除帶來的問題(子類繼承泛型方法的父類):
- 類型擦除與多態發生衝突
解決:編譯器合成橋方法(方法中調用子類新參數類型方法) - 返回類型更加嚴格的問題
解決:編譯器合成橋方法(虛擬機,用參數類型和返回類型來確定一個方法,編譯器產生僅有返回類型不同的兩個方法的字節碼,虛擬機能夠正確處理)
示例:DateInterval extends Pair ,覆蓋setSecond,getSecond方法
使用:javac命令編譯DateInterval類,然後執行javap -private DateInterval命令反編譯,可看到編譯器合成的橋方法。
注:橋方法的其他應用——覆蓋方法時可以指定更嚴格的返回類型。
翻譯泛型表達式
方法調用與存取泛型域時,編譯器插入強制類型轉換。
調用遺留代碼可能產生警告,可用註解@SuppressWarnings(“unchecked”)使其消失
小結
- 虛擬機中沒有泛型,只有普通的類和方法。
- 所有的類型參數都用它們的限定類型替換。
- 橋方法被合成來保持多態。
- 爲保持類型安全性,必要時插人強制類型轉換。
使用限制
大部分是由類型擦除引起
- 不能用基本類型實例化類型參數
- 運行時類型查詢只適用於原始類型
- 不能創建參數化類型的數組(聲明變量是合法的)
類型擦除,可能會將不相關的不同類型參數的實例添加到同一個數組中,引起內部邏輯錯誤; - varargs警告
參數個數可變的方法傳入泛型類型的實例。規則放鬆,但會警告,需要添加@SafeVarargs或者@SuppressWarnings - 不能實例化類型變量
解決方法:static Pair makePair(Supplier constr) 或者 Class.newInstance() - 不能構造泛型數組
- 若數組僅僅作爲類的私有實例域,可以聲明爲Object[],獲取元素時進行強制轉換;
- 用戶提供一個數組構造器表達式
public static <T extends Comparable〉T[] minmax(IntFunction constr, T… a){T口 mm = constr.apply(2);}
String[]::new
或使用方式:Array.newInstance
應用:ArrayList.toArray
- 泛型類的靜態上下文中類型變量無效
- 不能拋出或捕獲泛型類實例
- 可以消除對受查異常的檢查
- 注意擦除後的衝突
- 注意方法命名
- “要想支持擦除的轉換, 就需要強行限制一個類或類型變量不能同時成爲兩個接口類型的子類,而這兩個接口是同一接口的不同參數化”
繼承規則
- 無論S與T有什麼聯繫,通常,Pai
與Pair沒有什麼聯繫。 - 可以將參數化類型轉換爲原始類型
- 泛型類可以擴展或實現其他泛型類
通配符類型
概念
使用泛型時,允許參數類型變化的泛型稱爲通配符類型
public static void printBuddies(Pair p)
不允許將Pair傳遞給這個方法,但修改形參爲
Pair<? extends Employee> 後允許
按限定分類
-
子類型限定
Pair<? extends Employee>類型的變量不允許調用其setFirst方法,編譯器拒絕傳遞任何特定的類型
用來區分安全的訪問器方法和不安全的更改器方法。 -
超類型限定subtype bound
可以爲方法提供參數,但不能使用返回值 -
無限定通配符
不需要實際的類型
通配符捕獲
通配符捕獲只有在有許多限制的情況下才是合法的
反射和泛型
泛型Class類
它允許ClaSS方法的返回類型更加具有針對性
T newInstanceO
T cast(Object obj)
T getEnumConstants()
Class<? super T> getSuperclass()
Constructors getConstructor(C1ass… parameterTypes)
Constructors getDeclaredConstructor(Class… parameterTypes)
虛擬機中信息
TypeVariable:類型變量
WildcardType:通配符限定
ParameterizedType:泛型類或接口類型(如 Comparable<? super T>)
GenericArrayTYpe:泛型數組類型
集合
集合框架
基本結構
Iterable
Iterator:hasNext、next、remove、forEachRemaining
Collection:add、iterator、泛型實用方法
AbstractCollection:提供了部分方法的默認實現
List接口:定義了多個用於隨機訪問的方法
ListIterator:定義了一個在迭代器前面添加元素的方法
RandomAccess:標籤接口,標識集合實現類是否支持高效的隨機訪問方式
Set接口:等同於Collection,但並不是所有集都是集合,方便程序員編寫只接受集的方法。
SortedSet、SortedMap:定義了可以得到集合子集視圖的方法。(9.4)
NavigableSet、NavigableMap:用於搜索和遍歷有序集和映射的方法,TreeSet、TreeMap實現
具體集合
-
鏈表
數組和數組列表的重大缺陷:中間位置插入或刪除元素會付出很大代價(使用鏈表的唯一理由)
所有鏈表實際上是雙向鏈接的doubly linked,ListIterator接口定義了反向操作的方法- 添加元素
依賴位置的add方法由迭代器負責,對自然有序的集合使用迭代器添加元素纔有意義,所有add方法被定義在了ListIterator
可向前或向後插入元素,插入位置個數爲n+1個 - 遍歷
反向遍歷:hasPrevious、previous
不要使用隨機訪問方法遍歷鏈表,效率極低 - 刪除
可向前或向後刪除
- 添加元素
-
數組列表
-
散列集
裝填因子(load factor)決定何時對散列表進行再散列。表中超過75%的位置已經填入元素,表會用雙倍桶數自動再散列
HashSet:基於散列表的集
JavaSE8中,桶滿時會從鏈表編程平衡二叉樹 -
樹集
TreeSet是一個有序集合(Sorted Collection)排序使用樹結構完成(當前實現使用的是紅黑樹),實現了NavigableSet。
不需要對集中元素排序時,優先使用散列集。 -
隊列
檢索規則:先進先出
實現方式:循環數組、鏈表
ArrayDeque:有界集合容量有限
LinkedList:無界集合 -
優先級隊列(priority queue)
按任意順序插入,按排序順序檢索,調用remove方法,總會獲得當前隊列中優先級最小的元素。
迭代方式處理元素,不需要進行排序
使用結構:堆(heap)
使用示例:任務調度
映射
-
普通實現類
HashMap
TreeMap -
操作
put – 返回用這個鍵參數存儲的上一個值
更新映射
counts.put(word, counts.getOrDefault(word, 0)+ 1);
或
counts.putlfAbsent(word, 0);
counts.put(word, counts.get(word)+ 1); // Now we know that get will succeed
或
counts.merge(word, 1, Integer::sum); -
映射視圖
keySet、values、entrySet
可刪除元素,不能調用add增加元素 -
專用映射
- WeakHashMap:當對鍵的唯一引用來自散列條目時, 這一數據結構將與垃圾回收器協同工作一起刪除鍵/值對。
- LinkedHashSet 和 LinkedHashMap:
① 條目插入表中時,會併入雙向鏈表,記住插入項的順序。
② 鏈接散列映射可用訪問順序,對映射條目進行迭代。每次調用get或put, 受到影響的條目將從當前的位置刪除,並放到條目鏈表的尾部,LinkedHashMap<K, V>(initialCapacity, loadFactor, true)
③ 可覆蓋removeEldestEntry方法實現基於訪問順序的“最近最少使用”原則實現高速緩存 - EnumSet、EnumMap:鍵爲枚舉類型,可用靜態方法構造集
- IdentityHashMap:System.identityHashCode計算鍵,對象比較時使用==
視圖與包裝器
-
輕量級集合包裝器
Arrays.asList()
Collection.nCopies(100, ”xx“)
Collection.singleton() /singletonList /singletonMap -
子範圍
可應用任何操作 -
不可修改
Collections.unmodifiableCollection / xxxList / xxxSet …… -
同步
-
受查視圖
添加元素時,檢測是否符合定義時的泛型類型
算法
- 排序
- 二分查找
- 簡單算法
min、max、…… - 批操作
addAll、retainAll、…… - 數組轉換
遺留集合
- Hashtable
- 枚舉Enumeration
- 屬性映射Properties
- 棧Stack
- 位集BitSet
小結
本文整理了Java核心技術中泛型及集合相關的內容,Java中泛型的實現是編譯器的功能,將定義泛型的地方進行類型擦除,使用泛型的地方插入了強制類型轉換,避免了類定義數量過多的問題,增強了Java語言的安全性;集合框架提供了豐富的接口及實現類,方面功能開發,同時體現了泛型的具體應用。