1. 程式人生 > >第29條:優先考慮型別安全的異構容器

第29條:優先考慮型別安全的異構容器

術語 :

        有時會需要未限定固定數目的型別引數的容器,此時,可以將容器的鍵進行引數化而不是將容器引數化。然後將引數化的鍵交給容器來插入或者獲得值。用泛型系統來確保值的型別和它的鍵相符。示例程式碼如下:

// Typesafe heterogeneous container pattern - implementation
public class Favorites {
	private Map<Class<?>, Object> favorites = 
			new HashMap<Class<?>, Object>();
	public <T> void putFavorite(Class<T> 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));
	}
}

// Demo: Typesafe heterogeneous container pattern - client
public static void main(String[] args) {
	Favorites f = new Favorites();
	f.putFavorite(String.class, "Java");
	f.putFavorite(Integer.class, 0xcafebale);
	f.putFavorite(Class.class, Favorites.class);
	String favoriteString = f.getFavorite(String.class);
	int favoriteInteger = f.getFavorite(Integer.class);
	Class<?> favoriteClass = f.getFavorite(Class.class);
	System.out.printf("%s %x %s%n", favoriteString, favoriteInteger,
			                        favoriteClass);
}
        上面的程式碼中,class方法返回的是一個class<T>的形式。Favorites是型別安全的,它總是按照鍵返回正確的值,同時它也是異構的,因為它的容器的鍵不是同一種類型,這有別於傳統的Map,因此,將Favorites稱作型別安全的異構容器。異構來自哪?答案是無限制萬用字元的鍵Class<?>,在這裡它僅代表是某種class,因此允許將不同類的class放入同一個Map,這就是異構的原因。注意,因為我們使用的值型別是Object,因此Map並無法保證鍵一對能對應正確的值,它只知道值是一個Object就可以了,這種對應的關係是實現者自己來確保的。手動重新建立型別與值之間的關係是在getFavorite方法中進行的,利用Class的cast方法,將物件動態地轉換成Class物件所表示的型別。cast只是檢驗它的引數是不是為Class物件所表示的型別的例項。如果是,就返回引數,如果不是就丟擲ClassCastException。
public class Class<T> {
	T cast(Object obj);
}
        Favorites類有兩種侷限:一是惡意使用者可以通過使用原生態形式的Class來破壞年Favorites例項的型別安全。這種方式可以通知在putFavorite中進行型別檢查來確保例項物件進行檢查。
	public <T> void putFavorite(Class<T> type, T instance) {
		if (type == null)
			throw new NullPointerException("Type is null");
		favorites.put(type, type.cast(instance);
	}
        第二個侷限性在於它不能用在不可具體化的型別中。比如說可以儲存喜愛的String,String[],但是不能儲存List<String>。因為 List<String>.class是語法錯誤。因為在執行時他們的型別會被擦除,所在List<String>與List<Integer>實際上是共用一個Class。如果需要限制些可以傳遞給方法的型別,則可以使用有限制的萬用字元型別。
       
public <T extends Annotation>
      T getAnnotation(Class<T> annotationType);
        在這面這段程式碼裡,如果想把一個Class<?>傳遞給getAnnotation方法,那麼按照要求,可能想到可以將其轉換成Class<? extends Annotation>,但是這種行為是非受檢的,會收到編譯器的警告,但是,可以利用Class類提供的一個安全且動態地執行這種轉換的例項方法,asSubclass,它將呼叫它的Class物件轉換成用其引數表示的類的一個子型別,如果轉換成功,該方法就返回它的引數,如果失敗則丟擲ClassCastException。見如下例子:
public <T extends Annotation>
      T getAnnotation(Class<T> annotationType);

// Use of asSubclass to safely cast to a bounded type token
static Annotation getAnnocation(AnnocationElement element,
		                        String annotationTypeName) {
	Class<?> annotationType = null;// Unbounded type token
	try {
		annotationType = Class.forName(annotationTypeName);
	} catch (Exception ex) {
		throw new IllegalArgumentException(ex);
	}
	return element.getAnnotation {
		annotationType.asSubclass(Annotation.class);
	}
}