第29條:優先考慮型別安全的異構容器
阿新 • • 發佈:2019-02-04
術語 :
有時會需要未限定固定數目的型別引數的容器,此時,可以將容器的鍵進行引數化而不是將容器引數化。然後將引數化的鍵交給容器來插入或者獲得值。用泛型系統來確保值的型別和它的鍵相符。示例程式碼如下:
上面的程式碼中,class方法返回的是一個class<T>的形式。Favorites是型別安全的,它總是按照鍵返回正確的值,同時它也是異構的,因為它的容器的鍵不是同一種類型,這有別於傳統的Map,因此,將Favorites稱作型別安全的異構容器。異構來自哪?答案是無限制萬用字元的鍵Class<?>,在這裡它僅代表是某種class,因此允許將不同類的class放入同一個Map,這就是異構的原因。注意,因為我們使用的值型別是Object,因此Map並無法保證鍵一對能對應正確的值,它只知道值是一個Object就可以了,這種對應的關係是實現者自己來確保的。手動重新建立型別與值之間的關係是在getFavorite方法中進行的,利用Class的cast方法,將物件動態地轉換成Class物件所表示的型別。cast只是檢驗它的引數是不是為Class物件所表示的型別的例項。如果是,就返回引數,如果不是就丟擲ClassCastException。// 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); }
Favorites類有兩種侷限:一是惡意使用者可以通過使用原生態形式的Class來破壞年Favorites例項的型別安全。這種方式可以通知在putFavorite中進行型別檢查來確保例項物件進行檢查。public class Class<T> { T cast(Object obj); }
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。如果需要限制些可以傳遞給方法的型別,則可以使用有限制的萬用字元型別。在這面這段程式碼裡,如果想把一個Class<?>傳遞給getAnnotation方法,那麼按照要求,可能想到可以將其轉換成Class<? extends Annotation>,但是這種行為是非受檢的,會收到編譯器的警告,但是,可以利用Class類提供的一個安全且動態地執行這種轉換的例項方法,asSubclass,它將呼叫它的Class物件轉換成用其引數表示的類的一個子型別,如果轉換成功,該方法就返回它的引數,如果失敗則丟擲ClassCastException。見如下例子:public <T extends Annotation> T getAnnotation(Class<T> annotationType);
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);
}
}