優先考慮類型安全的異構容器

      泛型最常用於集合,如set和map,以及單元素的容器,如ThreadLocal和AtomicReference。在這些用法 中,它都充當被參數化了的容器。這樣就限制你每個容器只能有固定數目的類型參數。一般來說,這種情況正是你想要的。一個Set只有一個類型參數,表示它的元素類型;一個Map有兩個類型參數,表示它的鍵和值類型。

      但是有時候你會需要更多的靈活性。例如,數據庫行可以有任意多的列,如果能以類型安全的方式訪問所有列就好了。幸運的是,有一種辦法可以很容易地做到這一點。這種想法就是將鍵進行參數化而不是將容器進行參數化。然後將參數化的鍵提交給容器,來插入或者獲取值。用泛型系統來確保值的類型與它的鍵相符。

      通過Favorites來示範一下:

      Favorites是實例是類型安全的:當你向他請求String的時候,它從來不會返回一個Integer給你。同時它也是異構的(Heterogeneous):不像普通的Map,它的所有鍵都是不同類型的。因此,我們將Favorites稱作類型安全的異構容器(Typesafe Heterogenceous container)。

      Favorites小得出奇:

public class Favorites {
	private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
	
	public <T> void putFavorite(Class<?> type, T instance) {
		if (type == null) {
			throw new NullPointerException("type is null");
		}
		favorites.put(type, instance);
	}
	
	public <T> T getFavorite(Class<T> type) {
		return type.cast(favorites.get(type));
	}
}

這裏發生了一件微妙的事。每個Favorites實例都得到一個稱作favorites的私有Map<Class<?>, Object>的支持。你可能認爲由於無限制通配符類型的關係,將不能把任何東西放進這個Map中,但事實正好相反。要注意的是通配符類型是嵌套的:它不是屬於通配符類型的Map的類型,而是它的鍵的類型。由此可見,每個鍵都可以有一個不同的參數化類型:一個可以是Class<String>,接下來是Class<Integer>等等。異構就是從這裏來的。

        第二件要注意的事情是,favorites Map 的值類型只是Object。換句話說,Map並不能保證鍵和值之間的類型關係,即不能保證每個值的類型都與鍵的類型相同。事實上,Java的類型系統還沒有強大到這一點。但我們知道這是事實,並獲取favoite的時候利用了這一點。

        getFavorite方法的實現比putFavorite的更難一些。它先從favorites映射中獲得與指定Class對象相對應的值。這正是要返回的對象的引用,但它的編譯時類型是錯誤的。它的類型只是Object  (favorites映射的值類型),我們需要返回一個T。因此,getFavorite方法的實現利用Class 的cast方法,將對象引用動態地轉換(dynamically cast) 成了Class對象所表示的類型。

        cast方法是java的cast操作符的動態模擬。它只檢驗它的參數是否爲Class對象所表示的類型的實例。如果是,就返回參數;否則就拋出ClassCastExption異常。我們知道,getFavorite的cast調用永遠不會拋出ClassCastException異常,並假設客戶端代碼正確無誤地進行了編譯。也就是說,我們知道favorites映射中的值會始終與鍵的類型相匹配。

        Favorites類有兩種侷限性值得我們注意。首先,惡意的客戶端可以很輕鬆地破壞Favorites實例的類型安全,只要以它的原生態類型形式使用Class對象。但會造成客戶端代碼在編譯時產生未受檢的警告。這與一般的集合實現,如HashSet和HashMap並沒有什麼區別。你可以很容易地利用原生態類型HashSet將String放進HashSet<Integer>中。也就是說如果願意付出一點點代價,就可以擁有運行時的類型安全。確保Favorites永遠不違背它的類型約束條件的方式是:讓putfavorite方法檢驗instance是否真的是type所表示的類型的實例。我們已經知道這要如何進行了,只要使用珍上動態的轉換:

public <T> void putFavorite(Class<?> type, T instance) {
		if (type == null) {
			throw new NullPointerException("type is null");
		}
		favorites.put(type, type.cast(instance));
	}

         Favorites類的第二種侷限性在於它不能用在不可具體化的類型中。換句話說,你可以保存最喜愛的String或者String[] ,但不能保存最喜愛的List<String>。如果試圖保存最喜愛的List<String>,程序就不能進行編譯。原因在於你無法爲List<String> 獲得一個Class對象,即List.class。如果從"字面"上來看,List<String>.class和List<Integer>.class是合法的,並返回了相同的對象引用,就會破壞Favorites對象的內部結構。

         對於第二種侷限性還,還沒有完全令人滿意的解決辦法。有一種方法稱爲super type token。它在解決這一侷限性方面做了很多努力,但是這種方法仍有它自身的侷限性。

         Favorites使用的類型令牌是無限制的:getFavorite和putFaorite接受任何Class對象。有時候,可能需要限制那些可以傳給方法的類型。這可以通過有限制的類型令牌來實現,它吸是一個類型令牌,利用有限制類型參數或者有限制通配符,來限制可以表示 的類型。

        總而言之,集合API說明了泛型的一般用法,限制你每個容器只能有固定數目的類型參數。你可以通過將類型參數放在鍵上來避開。

        

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章