Item 41: Use marker interfaces to define types(使用標(biāo)記接口定義類型)

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”).

標(biāo)記接口是一種不包含任何方法聲明的接口,它只是指定(或「標(biāo)記」)一個(gè)類号枕,該類實(shí)現(xiàn)了具有某些屬性的接口醋粟。例如,考慮 Serializable 接口(Chapter 12)。通過實(shí)現(xiàn)此接口胰挑,表示類的實(shí)例可以寫入 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.

你可能聽過一個(gè)說法:標(biāo)記接口已經(jīng)過時(shí)众羡,更好的方式是標(biāo)記注解(Item-39)。這個(gè)言論是錯(cuò)誤的嗦嗡。與標(biāo)記注解相比,標(biāo)記接口有兩個(gè)優(yōu)點(diǎn)饭玲。首先侥祭,標(biāo)記接口定義的類型由標(biāo)記類的實(shí)例實(shí)現(xiàn);標(biāo)記注解不會(huì)茄厘。 標(biāo)記接口類型的存在允許你在編譯時(shí)捕獲錯(cuò)誤矮冬,如果你使用標(biāo)記注解亡脑,則在運(yùn)行時(shí)才能捕獲這些錯(cuò)誤炼彪。

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 標(biāo)記接口來表明一個(gè)類是可序列化的。ObjectOutputStream.writeObject 方法序列化傳遞給它的對(duì)象诱担,它要求其參數(shù)是可序列化的窑滞。假設(shè)該方法的參數(shù)類型是 Serializable琼牧,那么在編譯時(shí)(通過類型檢查)就會(huì)檢測(cè)到對(duì)不合適的對(duì)象進(jìn)行序列化的錯(cuò)誤。編譯時(shí)錯(cuò)誤檢測(cè)是使用標(biāo)記接口的目的哀卫,但不幸的是巨坊,ObjectOutputStream.writeObject 沒有利用 Serializable 接口:它的參數(shù)被聲明為 Object 類型,因此此改,如果嘗試序列化一個(gè)不可序列化對(duì)象趾撵,直到運(yùn)行時(shí)才會(huì)提示失敗。

譯注 1:原文 ObjectOutputStream.write 有誤共啃,該方法的每種重載僅支持 int 類型和 byte[]占调,應(yīng)修改為 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.

標(biāo)記接口相對(duì)于標(biāo)記注解的另一個(gè)優(yōu)點(diǎn)是可以更精確地定位它們移剪。 如果注解類型使用 @Target(ElementType.TYPE) 聲明究珊,它可以應(yīng)用于任何類或接口。假設(shè)你有一個(gè)只適用于特定接口來實(shí)現(xiàn)的標(biāo)記挂滓。如果將其定義為標(biāo)記接口苦银,則可以讓它擴(kuò)展其適用的惟一接口啸胧,確保所有標(biāo)記的類型也是其適用的惟一接口的子類型。

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 接口就是這樣一個(gè)受限的標(biāo)記接口纺念。它只適用于 Collection 的子類,但是除了 Collection 定義的方法之外想括,它不添加任何方法陷谱。它通常不被認(rèn)為是一個(gè)標(biāo)記接口,因?yàn)樗?xì)化了幾個(gè) Collection 方法的約定瑟蜈,包括 add烟逊、equals 和 hashCode。但是很容易想象一個(gè)標(biāo)記接口只適用于某些特定接口的子類铺根,而不細(xì)化任何接口方法的約定宪躯。這樣的標(biāo)記接口可能描述整個(gè)對(duì)象的某個(gè)不變量,或者表明實(shí)例能夠利用其他類的方法進(jìn)行處理(就像 Serializable 接口能夠利用 ObjectOutputStream 進(jìn)行處理一樣)位迂。

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.

相對(duì)于標(biāo)記接口访雪,標(biāo)記注解的主要優(yōu)勢(shì)是它們可以是其他注解功能的一部分。 因此掂林,標(biāo)記注解能夠與基于使用注解的框架保持一致性臣缀。

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.

那么什么時(shí)候應(yīng)該使用標(biāo)記注解,什么時(shí)候應(yīng)該使用標(biāo)記接口呢泻帮?顯然精置,如果標(biāo)記應(yīng)用于類或接口之外的任何程序元素,則必須使用標(biāo)記注解锣杂,因?yàn)橹挥蓄惡徒涌诓拍軐?shí)現(xiàn)或擴(kuò)展接口脂倦。如果標(biāo)記只適用于類和接口,那么可以問自己這樣一個(gè)問題:「我是否可以編寫一個(gè)或多個(gè)方法元莫,只接受具有這種標(biāo)記的對(duì)象狼讨?」如果是這樣,你應(yīng)該使用標(biāo)記接口而不是標(biāo)記注解柒竞。這將使你能夠?qū)⒔涌谟米飨嚓P(guān)方法的參數(shù)類型政供,這將帶來編譯時(shí)類型檢查的好處。如果你確信自己永遠(yuǎn)不會(huì)編寫只接受帶有標(biāo)記的對(duì)象的方法朽基,那么最好使用標(biāo)記注解布隔。此外,如果框架大量使用注解稼虎,那么標(biāo)記注解就是明確的選擇衅檀。

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.

總之,標(biāo)記接口和標(biāo)記注解都有各自的用途霎俩。如果你想要定義一個(gè)沒有與之關(guān)聯(lián)的新方法的類型哀军,可以使用標(biāo)記接口沉眶。如果你希望標(biāo)記類和接口之外的程序元素,或者將標(biāo)記符放入已經(jīng)大量使用注解類型的框架中杉适,那么標(biāo)記注解就是正確的選擇谎倔。如果你發(fā)現(xiàn)自己編寫的標(biāo)記注解類型有 @Target(ElementType.TYPE) 聲明(譯注:意在說明既可以用標(biāo)記注解,也可以用標(biāo)記接口的情況)猿推,那么請(qǐng)花時(shí)間弄清楚究竟應(yīng)該用注解類型片习,還是標(biāo)記接口更合適。

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 的說法相反藕咏,也就是說,「如果不想定義類型秽五,就不要使用接口孽查。」坦喘,與本條目應(yīng)用場(chǎng)景適應(yīng)的說法是卦碾,「如果你確實(shí)想定義類型,那么就要使用接口起宽。」


Back to contents of the chapter(返回章節(jié)目錄)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末济榨,一起剝皮案震驚了整個(gè)濱河市坯沪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌擒滑,老刑警劉巖腐晾,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丐一,居然都是意外死亡藻糖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門库车,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巨柒,“玉大人,你說我怎么就攤上這事柠衍⊙舐” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵珍坊,是天一觀的道長(zhǎng)牺勾。 經(jīng)常有香客問我,道長(zhǎng)阵漏,這世上最難降的妖魔是什么驻民? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任翻具,我火速辦了婚禮,結(jié)果婚禮上回还,老公的妹妹穿的比我還像新娘裆泳。我一直安慰自己,他們只是感情好懦趋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布晾虑。 她就那樣靜靜地躺著,像睡著了一般仅叫。 火紅的嫁衣襯著肌膚如雪帜篇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天诫咱,我揣著相機(jī)與錄音笙隙,去河邊找鬼。 笑死坎缭,一個(gè)胖子當(dāng)著我的面吹牛竟痰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掏呼,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼坏快,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了憎夷?” 一聲冷哼從身側(cè)響起莽鸿,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拾给,沒想到半個(gè)月后祥得,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蒋得,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年级及,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片额衙。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饮焦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窍侧,到底是詐尸還是另有隱情追驴,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布疏之,位于F島的核電站殿雪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锋爪。R本人自食惡果不足惜丙曙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一爸业、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亏镰,春花似錦扯旷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至逼肯,卻和暖如春耸黑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篮幢。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工大刊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人三椿。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓缺菌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親搜锰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伴郁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容