标记接口(marker interface) 是没有包含方法声明的接口,而只是指明(或者“标明”)一个类实现了具有某种属性的接口。例如,考虑Serilizable接口(第12章)。通过实现这个接口,表明类的实例可以被写到ObjectOutputStream(或者“被序列化”)。
??你可能听说过标记注解(第39项)使得标记接口过时了。这种断言是不准确的。标记接口有两点胜过标记注解。首先,也是最重要的一点,标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型。 标记接口类型的存在允许你在编译时捕获错误,如果使用标记注释,则只能在运行时才能捕获这些错误。
??Java的序列化工具(第6章)使用Serializable标记接口来指示类型是可序列化的。ObjectOutputStream.writeObject方法(序列化传递给它的对象)要求其参数可序列化。如果此方法的参数是Serializable类型,使用了不合适的对象尝试序列化,则在编译时(通过类型检查)就能检测出来。编译时错误检测是标记接口的目的,但不幸的是,ObjectOutputStream.write API没有利用Serializable接口:它的参数被声明为Object类型,因此尝试序列化不可序列化的对象时只有到运行时才会失败。
??标记接口胜过标记注解的另一个优点时,它们可以被更加精确地进行锁定。 如果注解类型利用ElementType.TYPE声明,它就可以被应用到任何类或者接口。假设有一个标记只适用于特殊接口的实现。如果将它定义成一个标记接口,就可以用它唯一的接口扩展成它使用的接口,从而保证所有标记类型也是使用的唯一接口的子类型。
??可以说,Set接口就是这样一个受限制的标记接口。它仅适用于Collection子类型,但它不会添加除Collection定义的方法之外的任何方法。它通常不被认为是标记接口,因为它改进了几个Collection方法的契约,包括add,equals和hashCode。但是很容易想象只适用于某种特殊接口的子类型的标记接口,它没有改进接口的任何方法的契约。这种标记接口可以描述整个对象的某个约束条件,或者表明实例能够利用其他某个类的方法进行处理(就像Serializable接口表明实例可以通过ObjectOutputStream进行处理一样)。
??标记注解优于标记接口的最大优点在于,它们是更大的注解机制的一部分。因此,标记注解在那些支持注解作为编程元素之一的框架中同样具有一致性。
??那么什么时候应该使用标记注解,什么时候应该使用标记接口呢?很显然,如果标记是应用到任何程序元素而不是类或者接口,就必须使用注解,因为只有类和接口可以用来实现或扩展接口。如果标记只应用给类和接口,就要问问自己:我要编写一个还是多个只接受有这种标记的方法呢?如果是这种情况,就应该优先使用标记接口而非注解。这样你就可以用接口作为相关方法的参数类型,它真正可以为你提供编译时进行类型检查的好处。如果你可以说服自己,永远不想写一个只接受带有标记的对象的方法,那么你最好使用标记注释。此外,如果标记是大量使用注释的框架的一部分,那么标记注释是明确的选择。
??总而言之,标记接口和标记注解都各有用处。如果想要定义一个任何新方法都不会与之关联的类型,标记接口就是最好的选择。如果想要标记程序元素而非类和接口,考虑到未来可能要给标记添加更多的信息,或者标记要适合于已经广泛使用了注解类型的框架,那么标记注解就是正确的选择。如果你发现自己在编写的是目标为ElementType.TYPE的标记注解类型,就要花点时间考虑清楚,它是否真的应该为注解类型,想想标记接口是否会更加合适呢。
??从某种意义上说,本项与22项中“如果不想定义类型就不要使用接口”的说法相反。本项最接近的意思是说:如果想要定义类型,一定要使用接口。