ITEM 41: USE MARKER INTERFACES TO DEFINE TYPES
标记接口是一个不包含任何方法声明,只是指定(或“标记”)实现接口的类具有某些属性的接口。例如,考虑 Serializable 接口(第12章)。通过实现这个接口,一个类表明它的实例可以被写入ObjectOutputStream (或“序列化”)。
您可能听说过标记注解(item 39)已经取代了标记接口。这个断言是不正确的。与标记注解相比,标记接口有两个优点。首先,标记接口定义由被标记类的实例实现的类型,标记注解不会。标记接口类型的存在允许您在编译时捕获错误,如果使用标记注解,则直到运行时才能捕获这些错误。
Java的序列化功能(第6章)使用 Serializable 标记器接口来指示类型是可序列化的。
ObjectOutputStream.writeObject 方法序列化传递给它的对象,这个方法要求它的参数是可序列化的。如果此方法的参数类型为 Serializable,则会在编译时(通过类型检查)检测到序列化不适当对象的尝试。编译时错误检测是标记接口的目的,但不幸的是,ObjectOutputStream.write API没有利用 Serializable 接口:它的参数被声明为Object 类型的,所以尝试序列化一个不可序列化的对象,运行时才会发现失败。
与标记注解相比,标记接口的另一个优点是可以更精确地定位它们。如果一个注解能够被应用于ElementType.type,那么他就能被应用到任何类或接口。假设您有一个仅适用于特定接口实现的标记。如果您将它定义为一个标记接口,您可以让它扩展它所适用的唯一接口,确保所有标记的类型也是它所适用的唯一接口的子类型。
可以说,Set 接口就是这样一个受限的标记接口。它只适用于 Collection 子类型,除了 Collection 定义的方法之外,它不添加任何方法。它通常不被认为是一个标记接口,因为它细化了几个集合方法的契约,包括 add、equals 和 hashCode。但是很容易想象一个标记接口,它只适用于某些特定接口的子类型,而不细化接口的任何方法的契约。这样的标记接口可以描述整个对象的某个不变量,或者指示实例有资格通过其他某个类的方法进行处理(就像 Serializable 接口指示实例有资格通过ObjectOutputStream 进行处理一样)。
与标记接口相比,标记注解的主要优点是它们是大型注解工具的一部分。因此,标记注解允许在基于注解的框架中保持一致性。
那么什么时候应该使用标记注解,什么时候应该使用标记接口呢?显然,如果标记应用于类或接口之外的任何程序元素,则必须使用注解,因为只有类和接口才能实现或扩展接口。如果标记只适用于类和接口,请自问“我是否需要编写一个或多个只接受具有此标记的对象的方法?” 如果是这样,您应该使用标记接口而不是注解。这将使您能够将接口用作相关方法的参数类型,这将带来编译时类型检查的好处。如果您能够说服自己,您永远不会想要编写一个只接受带有标记的对象的方法,那么您最好使用标记注解。此外,如果标记是大量使用注解的框架的一部分,那么标记注解是明显的选择。
总之,标记接口和标记注解都有各自的用途。如果您想定义一个没有任何新方法与之关联的类型,那么标记接口是最好的选择。如果您想标记类和接口之外的程序元素,或者将标记放入已经大量使用注解类型的框架中,那么标记注解是正确的选择。如果您发现自己正在编写一个目标为 ElementType.TYPE 的标记注解类型,花点时间弄清楚它究竟应该是注解类型还是标记接口更合适。
从某种意义上说,这一项与第22项正好相反,第22项说:“如果不想定义类型,就不要使用接口。”根据初步估计,这一项表示:“如果您确实想定义类型,请使用接口。”
网友评论