Item 41: Use marker interfaces to define types(使用标记接口定义类型)

A marker interface is an interface that contains no method declarations but merely designates (or “marks”) a class that implements the interface as having some property. For example, consider the Serializable interface (Chapter 12). By implementing this interface, a class indicates that its instances can be written to an ObjectOutputStream (or “serialized”).

标记接口是一种不包含任何方法声明的接口,它只是指定(或「标记」)一个类,该类实现了具有某些属性的接口。例如,考虑 Serializable 接口(Chapter 12)。通过实现此接口,表示类的实例可以写入 ObjectOutputStream(或「序列化」)。

You may hear it said that marker annotations (Item 39) make marker interfaces obsolete. This assertion is incorrect. Marker interfaces have two advantages over marker annotations. First and foremost, marker interfaces define a type that is implemented by instances of the marked class; marker annotations do not. The existence of a marker interface type allows you to catch errors at compile time that you couldn’t catch until runtime if you used a marker annotation.

你可能听过一个说法:标记接口已经过时,更好的方式是标记注解(Item-39)。这个言论是错误的。与标记注解相比,标记接口有两个优点。首先,标记接口定义的类型由标记类的实例实现;标记注解不会。 标记接口类型的存在允许你在编译时捕获错误,如果你使用标记注解,则在运行时才能捕获这些错误。

Java’s serialization facility (Chapter 6) uses the Serializable marker interface to indicate that a type is serializable. The ObjectOutputStream.writeObject method, which serializes the object that is passed to it, requires that its argument be serializable. Had the argument of this method been of type Serializable, an attempt to serialize an inappropriate object would have been detected at compile time (by type checking). Compile-time error detection is the intent of marker interfaces, but unfortunately, the ObjectOutputStream.write API does not take advantage of the Serializable interface: its argument is declared to be of type Object, so attempts to serialize an unserializable object won’t fail until runtime.

Java 的序列化工具(Chapter 6)使用 Serializable 标记接口来表明一个类是可序列化的。ObjectOutputStream.writeObject 方法序列化传递给它的对象,它要求其参数是可序列化的。假设该方法的参数类型是 Serializable,那么在编译时(通过类型检查)就会检测到对不合适的对象进行序列化的错误。编译时错误检测是使用标记接口的目的,但不幸的是,ObjectOutputStream.writeObject 没有利用 Serializable 接口:它的参数被声明为 Object 类型,因此,如果尝试序列化一个不可序列化对象,直到运行时才会提示失败。

译注 1:原文 ObjectOutputStream.write 有误,该方法的每种重载仅支持 int 类型和 byte[],应修改为 ObjectOutputStream.writeObject,其源码如下:

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}

译注 2:使用 ObjectOutputStream.writeObject 的例子

public class BaseClass implements Serializable {
    private final int id;
    private final String name;

    public BaseClass(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "id=" + id + ", name='" + name + '\'';
    }
}

public class Main {
    private void Out() throws IOException {
        BaseClass obj = new BaseClass(1, "Mark");
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File("out.txt")))) {
            out.writeObject(obj);
        }
    }

    private void In() throws IOException, ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(new File("out.txt")))) {
            BaseClass obj = (BaseClass) in.readObject();
            System.out.println(obj);
        }
    }
}

Another advantage of marker interfaces over marker annotations is that they can be targeted more precisely. If an annotation type is declared with target ElementType.TYPE, it can be applied to any class or interface. Suppose you have a marker that is applicable only to implementations of a particular interface. If you define it as a marker interface, you can have it extend the sole interface to which it is applicable, guaranteeing that all marked types are also subtypes of the sole interface to which it is applicable.

标记接口相对于标记注解的另一个优点是可以更精确地定位它们。 如果注解类型使用 @Target(ElementType.TYPE) 声明,它可以应用于任何类或接口。假设你有一个只适用于特定接口来实现的标记。如果将其定义为标记接口,则可以让它扩展其适用的惟一接口,确保所有标记的类型也是其适用的惟一接口的子类型。

Arguably, the Set interface is just such a restricted marker interface. It is applicable only to Collection subtypes, but it adds no methods beyond those defined by Collection. It is not generally considered to be a marker interface because it refines the contracts of several Collection methods, including add, equals, and hashCode. But it is easy to imagine a marker interface that is applicable only to subtypes of some particular interface and does not refine the contracts of any of the interface’s methods. Such a marker interface might describe some invariant of the entire object or indicate that instances are eligible for processing by a method of some other class (in the way that the Serializable interface indicates that instances are eligible for processing by ObjectOutputStream).

可以说,Set 接口就是这样一个受限的标记接口。它只适用于 Collection 的子类,但是除了 Collection 定义的方法之外,它不添加任何方法。它通常不被认为是一个标记接口,因为它细化了几个 Collection 方法的约定,包括 add、equals 和 hashCode。但是很容易想象一个标记接口只适用于某些特定接口的子类,而不细化任何接口方法的约定。这样的标记接口可能描述整个对象的某个不变量,或者表明实例能够利用其他类的方法进行处理(就像 Serializable 接口能够利用 ObjectOutputStream 进行处理一样)。

The chief advantage of marker annotations over marker interfaces is that they are part of the larger annotation facility. Therefore, marker annotations allow for consistency in annotation-based frameworks.

相对于标记接口,标记注解的主要优势是它们可以是其他注解功能的一部分。 因此,标记注解能够与基于使用注解的框架保持一致性。

So when should you use a marker annotation and when should you use a marker interface? Clearly you must use an annotation if the marker applies to any program element other than a class or interface, because only classes and interfaces can be made to implement or extend an interface. If the marker applies only to classes and interfaces, ask yourself the question “Might I want to write one or more methods that accept only objects that have this marking?” If so, you should use a marker interface in preference to an annotation. This will make it possible for you to use the interface as a parameter type for the methods in question, which will result in the benefit of compile-time type checking. If you can convince yourself that you’ll never want to write a method that accepts only objects with the marking, then you’re probably better off using a marker annotation. If, additionally, the marking is part of a framework that makes heavy use of annotations, then a marker annotation is the clear choice.

那么什么时候应该使用标记注解,什么时候应该使用标记接口呢?显然,如果标记应用于类或接口之外的任何程序元素,则必须使用标记注解,因为只有类和接口才能实现或扩展接口。如果标记只适用于类和接口,那么可以问自己这样一个问题:「我是否可以编写一个或多个方法,只接受具有这种标记的对象?」如果是这样,你应该使用标记接口而不是标记注解。这将使你能够将接口用作相关方法的参数类型,这将带来编译时类型检查的好处。如果你确信自己永远不会编写只接受带有标记的对象的方法,那么最好使用标记注解。此外,如果框架大量使用注解,那么标记注解就是明确的选择。

In summary, marker interfaces and marker annotations both have their uses. If you want to define a type that does not have any new methods associated with it, a marker interface is the way to go. If you want to mark program elements other than classes and interfaces or to fit the marker into a framework that already makes heavy use of annotation types, then a marker annotation is the correct choice. If you find yourself writing a marker annotation type whose target is ElementType.TYPE, take the time to figure out whether it really should be an annotation type or whether a marker interface would be more appropriate.

总之,标记接口和标记注解都有各自的用途。如果你想要定义一个没有与之关联的新方法的类型,可以使用标记接口。如果你希望标记类和接口之外的程序元素,或者将标记符放入已经大量使用注解类型的框架中,那么标记注解就是正确的选择。如果你发现自己编写的标记注解类型有 @Target(ElementType.TYPE) 声明(译注:意在说明既可以用标记注解,也可以用标记接口的情况),那么请花时间弄清楚究竟应该用注解类型,还是标记接口更合适。

In a sense, this item is the inverse of Item 22, which says, “If you don’t want to define a type, don’t use an interface.” To a first approximation, this item says, “If you do want to define a type, do use an interface.”

从某种意义上说,本条目与 Item-22 的说法相反,也就是说,「如果不想定义类型,就不要使用接口。」,与本条目应用场景适应的说法是,「如果你确实想定义类型,那么就要使用接口。」


Back to contents of the chapter(返回章节目录)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容