??泛型的常見用途是用于集合,比如Set<E>和Map<K,V>,以及單元素容器,比如ThreadLocal<T>和AtomicReference<T>。在所有這些用途中,容器都是參數(shù)化的。這限制了你在每個(gè)容器中有固定數(shù)量的類型參數(shù)。通常這就是你想要的。Set有一個(gè)單獨(dú)的類型參數(shù),代表了它的元素類型;Map有兩個(gè),代表了它的key和value類型;諸如此類。
??然而有時(shí)候,你需要更多的靈活性。比如,一個(gè)數(shù)據(jù)庫行可以擁有任意多的列,如果它有一個(gè)類型安全的方法來訪問所有列那就太好了。幸運(yùn)的是,有一個(gè)簡單的方式來實(shí)現(xiàn)這個(gè)效果。這個(gè)想法是將key參數(shù)化,而不是將容器參數(shù)化。然后將容器顯示參數(shù)化的key,以插入或檢索value。泛型類型系統(tǒng)用于保證value的類型與其key一致。
??作為這個(gè)方法的簡單例子,考慮使用Favorites類允許它的客戶端來存儲和檢索任意多類型的favorite實(shí)例。Class對象將扮演參數(shù)化key的角色。這樣可以工作的原因是Class類是泛型。類文字的類型不是簡單的Class而是Class<T>.比如,String.class是Class<String>,Integer.class是Class<Integer>。當(dāng)類文字在方法之間傳遞以同時(shí)通信編譯時(shí)和運(yùn)行時(shí)類型信息時(shí),它被稱為*類型令牌 *[Bracha04]。
??Favorites類的API是簡單的。它看起來只是一個(gè)簡單map,除了它的key是參數(shù)化的而不是map??蛻舳嗽谠O(shè)置和獲取favorites時(shí)顯示一個(gè)Class對象。這是它的API:

??如下是一個(gè)簡單的程序使用Favorites類,存儲,檢索和打印一個(gè)favorite String,Integer,Class實(shí)例:


??正如你所期望的,這個(gè)程序打印了“Java cafebabe Favorites”.請注意,順便提一下,Java的printf方法和C的不同,你應(yīng)該使用%n而在C中使用的是\n。%n生成適用于特定平臺的行分隔符,該分隔符在許多但不是所有的平臺上都是\n。
??一個(gè)Favorites實(shí)例是類型安全的:當(dāng)你問它要一個(gè)String的時(shí)候它永遠(yuǎn)不會返回一個(gè)Integer。它也是異構(gòu)的:不像一個(gè)原始map。所有key都是不同類型的。所以,我們叫Favorites是類型安全的異構(gòu)容器。
??Favorites的實(shí)現(xiàn)是令人驚訝的瘦小,這是它的完整實(shí)現(xiàn):

??這里發(fā)生了一些微妙的事情。每個(gè)Favorites實(shí)例都基于一個(gè)叫做favorites的私有的Map<Class<?>,Object>.你可能會認(rèn)為你不能在這個(gè)map中放入任何因?yàn)闊o界通配符類型,但是實(shí)際上它可以。需要注意的是,通配符類型是嵌套的:不是map的類型是通配符類型,而是key的類型是。這意味著每一個(gè)key都可以有不同的參數(shù)化類型:其中一個(gè)可以是Class<String>,下一個(gè)可以是Class<Integer>,諸如此類。這就是異構(gòu)的來源。
??接下來要注意的是,F(xiàn)avorites map的值類型只是一個(gè)對象。換句話說,Map并不保證key和value之間的類型關(guān)系,即每個(gè)value都是由其key表示的類型。事實(shí)上,Java的類型系統(tǒng)并不足夠強(qiáng)大來表達(dá)這一點(diǎn)。但是我們知道這是真的,我們利用它來檢索favorite。
??putFavorite實(shí)現(xiàn)是簡單的:它簡單地將一個(gè)從給定的Class對象到給定的favorite實(shí)例的映射放到favorites中。如前所述,這放棄了key和value的“類型鏈接”;它失去了該value是key的一個(gè)實(shí)例的知識。但這沒關(guān)系,因?yàn)間etFavorites方法可以重新建立這個(gè)關(guān)系。
??getFavorite的實(shí)現(xiàn)比putFavorite復(fù)雜。首先,它從favorites map中獲取對應(yīng)于給定Class對象的值。這時(shí)應(yīng)該返回正確的對象引用,但是它的編譯時(shí)類型是錯(cuò)誤的:他是Object(favorites map的value的類型),我們需要返回一個(gè)T。所以,getFavorite動(dòng)態(tài)地通過Class對象代表的類型實(shí)現(xiàn)了強(qiáng)制類型轉(zhuǎn)換對象,使用了Class的強(qiáng)制轉(zhuǎn)換方法。
??強(qiáng)轉(zhuǎn)方法是與Java的強(qiáng)轉(zhuǎn)操作符的動(dòng)態(tài)模擬。它只是檢查它的參數(shù)是否是Class對象表示的類型的實(shí)例。如果是,它返回參數(shù);否則會拋出ClassCastException。我們知道getFavorite中的強(qiáng)制轉(zhuǎn)換調(diào)用不會拋出ClassCastException,假設(shè)客戶端代碼編譯干凈。也就是說,我們知道favorites map的value總與它們的key的類型相匹配。
??那么cast方法為我們做了什么呢,它只是簡單地返回了它的參數(shù)嗎?cast方法的簽名充分利用了類是泛型的事實(shí)。它的返回類型是Class對象的類型參數(shù):

??這正是getFavorite方法所需要的。這就允許我們在不依賴于T的未檢查的強(qiáng)轉(zhuǎn)的情況下使得Favorites類型安全。
??Favorites類有兩個(gè)值得注意的限制。首先,一個(gè)有惡意的客戶端會通過使用原始類型的Class對象來容易地破壞favorites實(shí)例的類型安全性。但是,生成的客戶端代碼在編譯時(shí)會生成未經(jīng)檢查的警告。這與像HashSet和HashMap這樣的正常集合實(shí)現(xiàn)沒有什么不同。你可以很容易地將一個(gè)String放入HashSet<Integer>,通過原始類型HashSet(item26)。也就是說,如果你愿意未運(yùn)行時(shí)類型的安全性付出代價(jià),就可以保證它的安全性。確保Favorite不會違反其類型不變的方法是使用putFavorite方法來檢查實(shí)例確實(shí)是這個(gè)類型代表的類型,我們已經(jīng)知道怎么做了,只要實(shí)用動(dòng)態(tài)轉(zhuǎn)換:

??java.util.Collections中有一些集合包裝器,他們可以玩同樣的把戲。它們叫做checkedSet,checkedList,checkedMap諸如此類。它們的靜態(tài)工廠除了集合(或map)還接受一個(gè)Class對象(或兩個(gè))。靜態(tài)工廠是泛型方法,確保Class對象和集合在編譯時(shí)類型匹配。包裝器為它們包裝的集合添加具體化。比如,如果有人嘗試將Coin放入你的Collection<Stamp>,包裝器就會拋出ClassCastException。這些包裝器對于跟蹤將錯(cuò)誤類型元素添加倒集合中的客戶端代碼非常有用,該應(yīng)用程序混合了泛型和原始類型。
??Favorites類的第二個(gè)限制是它不能用于不可還原的類型(item28)。換句話說,你可以將String或者String[]存儲到你的favorite中,但是List<String>不可以。如果你嘗試存儲List<String>,你的程序?qū)⒉荒芫幾g。 原因是你不能為List<String>得到一個(gè)Class對象。類文字List<String>.class是一個(gè)語法錯(cuò)誤,同時(shí)這也是一件好事。List<String>和List<Integer>共享一個(gè)簡單Class對象,是List.class。如果“類型文字”List<String>.class和List<Integer>.class是合法的,并且返回相同的對象引用,那么它將會破壞Favorites對象的內(nèi)部。對于這一限制,沒有完全令人滿意的解決辦法。
??Favorites實(shí)用的類型標(biāo)記是無界的:getFavorite和putFavorite接受任何Class對象。有時(shí)候你可能需要去限制可以傳遞給方法的類型。這可以通過有界類型令牌來實(shí)現(xiàn),它只是一個(gè)類型標(biāo)記,它對可以表示的類型進(jìn)行綁定,使用有界類型參數(shù)(item30 )或有界通配符(item31)。
??注解API(item39 )廣泛使用有界類型標(biāo)記。例如,下面是運(yùn)行時(shí)讀取注解的方法。這個(gè)方法來自AnnotatedElement接口,該接口由表示類,方法,字段和其他程序元素的反射類型實(shí)現(xiàn):

??參數(shù)注釋類似是一個(gè)有界類型標(biāo)記,表示注釋類型。如果該方法有該元素的注解,則返回該元素的注解,如果它沒有,則返回null。本質(zhì)傷,帶注解的元素是一個(gè)類型安全的異構(gòu)容器,它的key是注解類型。
??假設(shè)你有一個(gè)類型為Class<?>的對象,并且希望將它傳遞給一個(gè)需要有界類型令牌的方法,例如getAnnotation。你可以強(qiáng)制將對象轉(zhuǎn)換為Class<? extends Annotation>,但是這個(gè)強(qiáng)制轉(zhuǎn)換是未經(jīng)檢查的,所以它會生成一個(gè)編譯時(shí)警告( item27)。幸運(yùn)的是,類Class提供了一個(gè)實(shí)例方法,可以安全地(動(dòng)態(tài)地)執(zhí)行這種類型的強(qiáng)制轉(zhuǎn)換。這個(gè)方法叫作asSubclass,它轉(zhuǎn)換了類對象,在該對象上調(diào)用它來表示由其參數(shù)表示的類的子類。如果強(qiáng)轉(zhuǎn)成功了,方法返回它的參數(shù);如果失敗了,它就拋出ClassCastException。
??下面是如何使用asSubclass方法讀取在編譯時(shí)類型未知的注解。這個(gè)方法編譯時(shí)不會有錯(cuò)誤或警告:

??總之,泛型的正常使用,以集合的API為例,將每個(gè)容器的類型參數(shù)限制為固定數(shù)量。你可以將type參數(shù)放置在key上而不是容器上來繞過這個(gè)限制。你可以使用Class對象用作此類類型安全的異構(gòu)容器的key。以這種方法使用的Class對象稱為類型令牌。你也可以使用自定義的key類型。例如,你可以使用DatabaseRow類型代表一個(gè)數(shù)據(jù)庫列(容器),泛型類型Column<T>作為它的key。
本文寫于2019.7.4,歷時(shí)2天