Walt You - 行是知之始

《Effective Java》学习日志(五)41:使用标记接口定义类型

2018-11-25

标记接口(marker interface),不包含方法声明,只是指定(或“标记”)一个类实现了具有某些属性的接口。


学习资料主要参考: 《Effective Java Third Edition》,作者:Joshua Bloch



例如,考虑Serializable接口(第12章)。 通过实现这个接口,一个类表明它的实例可以写入ObjectOutputStream(或“序列化”)。

你可能会听说过标记注解(Item 39)标记一个接口是废弃过时的。 这个断言是不正确的。 标记接口与标记注解相比具有两个优点。 首先,标记接口定义了一个由标记类实例实现的类型;标记注解则不会。 标记接口类型的存在允许在编译时捕获错误,如果使用标记注解,则直到运行时才能捕获错误。

Java的序列化机制(第6章)使用Serializable标记接口来指示某个类型是可序列化的。 对传递给它的对象进行序列化的ObjectOutputStream.writeObject方法要求其参数可序列化。 如果此方法的参数是Serializable类型,则在编译时会检测到序列化不适当对象的尝试(通过类型检查)。

编译时错误检测是标记接口的意图,但不幸的是,ObjectOutputStream.write API没有利用Serializable接口: 它的参数被声明为Object类型,所以尝试序列化一个不可序列化的对象直到运行时才会失败。

标记接口对于标记注解的另一个优点是可以更精确地定位目标。 如果使用目标ElementType.TYPE声明注解类型,它可以应用于任何类或接口。 假设有一个标记仅适用于特定接口的实现。 如果将其定义为标记接口,则可以扩展它适用的唯一接口,保证所有标记类型也是适用的唯一接口的子类型。

可以说,Set接口就是这样一个受限的标记接口。 它仅适用于Collection子类型,但不会添加超出Collection定义的方法。 它通常不被认为是标记接口,因为它改进了几个Collection方法的契约,包括add,equals和hashCode。 但很容易想象一个标记接口,它仅适用于某些特定接口的子类型,并且不会改进任何接口方法的契约。 这样的标记接口可以描述整个对象的一些约束条件(invariant),或者说明实例有资格被某个其他类的方法处理(就像Serializable接口指示实例有资格被ObjectOutputStream处理的方式)。

标记注解优于标记接口的主要优点是它们是较大的注解工具的一部分。因此,标记注解允许在基于注解的框架中保持一致性。

所以什么时候应该使用标记注解,什么时候应该使用标记接口? 显然,如果标记适用于除类或接口以外的任何程序元素,则必须使用注解,因为只能使用类和接口来实现或扩展接口。 如果标记仅适用于类和接口,那么问自己问题:“可能我想编写一个或多个只接受具有此标记的对象的方法呢?” 如果是这样,则应该优先使用标记接口而不是注解。 这将使你可以将接口用作所讨论方法的参数类型,这将带来编译时类型检查的好处。 如果你能说服自己,永远不会想写一个只接受带有标记的对象的方法,那么最好使用标记注解。 另外,如果标记是大量使用注解的框架的一部分,则标记注解是明确的选择。

总之,标记接口和标记注释都有其用处。 如果你想定义一个没有任何关联的新方法的类型,一个标记接口是一种可行的方法。 如果要标记除类和接口以外的程序元素,或者将标记符合到已经大量使用注解类型的框架中,那么标记注解是正确的选择。 如果发现自己正在编写目标为ElementType.TYPE的标记注解类型,请花点时间确定它是否应该是注释类型,是不是标记接口是否更合适。

从某种意义来说,本Item与Item22的的意思正好相反,Item22的意思是:“如果你不想定义一个类型,不要使用接口”。 本Item的意思是:如果想定义一个类型,一定要使用接口。


Content