Effective Java 第三版——33. 优先考虑类型安全的异构容器 (2)

getFavorite的实现比putFavorite更复杂。 首先,它从favorites Map中获取与给定Class对象相对应的值。 这是返回的正确对象引用,但它具有错误的编译时类型:它是Object(favorites map的值类型),我们需要返回类型T。因此,getFavorite实现动态地将对象引用转换为Class对象表示的类型,使用Class的cast方法。

cast方法是Java的cast操作符的动态模拟。它只是检查它的参数是否由Class对象表示的类型的实例。如果是,它返回参数;否则会抛出ClassCastException异常。我们知道,假设客户端代码能够干净地编译,getFavorite中的强制转换不会抛出ClassCastException异常。 也就是说,favorites map中的值始终与其键的类型相匹配。

那么这个cast方法为我们做了什么,因为它只是返回它的参数? cast的签名充分利用了Class类是泛型的事实。 它的返回类型是Class对象的类型参数:

public class Class<T> { T cast(Object obj); }

这正是getFavorite方法所需要的。 这正是确保Favorites类型安全,而不用求助一个未经检查的强制转换的T类型。

Favorites类有两个限制值得注意。 首先,恶意客户可以通过使用原始形式的Class对象,轻松破坏Favorites实例的类型安全。 但生成的客户端代码在编译时会生成未经检查的警告。 这与正常的集合实现(如HashSet和HashMap)没有什么不同。 通过使用原始类型HashSet(条目 26),可以轻松地将字符串放入HashSet <Integer>中。 也就是说,如果你愿意为此付出一点代价,就可以拥有运行时类型安全性。 确保Favorites永远不违反类型不变的方法是,使putFavorite方法检查该实例是否由type表示类型的实例,并且我们已经知道如何执行此操作。只需使用动态转换:

// Achieving runtime type safety with a dynamic cast public <T> void putFavorite(Class<T> type, T instance) { favorites.put(type, type.cast(instance)); }

java.util.Collections中有一些集合包装类,可以发挥相同的诀窍。 它们被称为checkedSet,checkedList,checkedMap等等。 他们的静态工厂除了一个集合(或Map)之外还有一个Class对象(或两个)。 静态工厂是泛型方法,确保Class对象和集合的编译时类型匹配。 包装类为它们包装的集合添加了具体化。 例如,如果有人试图将Coin放入你的Collection <Stamp>中,则包装类在运行时会抛出ClassCastException。 这些包装类对于追踪在混合了泛型和原始类型的应用程序中添加不正确类型的元素到集合的客户端代码很有用。

Favorites类的第二个限制是它不能用于不可具体化的(non-reifiable)类型(条目 28)。 换句话说,你可以保存你最喜欢的String或String [],但不能保存List <String>。 如果你尝试保存你最喜欢的List <String>,程序将不能编译。 原因是无法获取List <String>的Class对象。 List <String> .class是语法错误,也是一件好事。 List <String>和List <Integer>共享一个Class对象,即List.class。 如果“字面类型(type literals)”List <String> .class和List <Integer> .class合法并返回相同的对象引用,那么它会对Favorites对象的内部造成严重破坏。 对于这种限制,没有完全令人满意的解决方法。

Favorites使用的类型令牌( type tokens)是无限制的:getFavorite和putFavorite接受任何Class对象。 有时你可能需要限制可传递给方法的类型。 这可以通过一个有限定的类型令牌来实现,该令牌只是一个类型令牌,它使用限定的类型参数(条目 30)或限定的通配符(条目 31)来放置可以表示的类型的边界。

注解API(条目 39)广泛使用限定类型的令牌。 例如,以下是在运行时读取注解的方法。 此方法来自AnnotatedElement接口,该接口由表示类,方法,属性和其他程序元素的反射类型实现:

public <T extends Annotation> T getAnnotation(Class<T> annotationType);

参数annotationType是表示注解类型的限定类型令牌。 该方法返回该类型的元素的注解(如果它有一个);如果没有,则返回null。 本质上,注解元素是一个类型安全的异构容器,其键是注解类型。

假设有一个Class <?>类型的对象,并且想要将它传递给需要限定类型令牌(如getAnnotation)的方法。 可以将对象转换为Class<? extends Annotation>,但是这个转换没有被检查,所以它会产生一个编译时警告(条目 27)。 幸运的是,Class类提供了一种安全(动态)执行这种类型转换的实例方法。 该方法被称为asSubclass,并且它转换所调用的Class对象来表示由其参数表示的类的子类。 如果转换成功,该方法返回它的参数;如果失败,则抛出ClassCastException异常。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypzyz.html