优先考虑类型安全的异构容器

      泛型最常用于集合,如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说明了泛型的一般用法,限制你每个容器只能有固定数目的类型参数。你可以通过将类型参数放在键上来避开。

        

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