[英]Marker Interfaces in Java?
我被告知 Java 中的 Marker 接口是一個空接口,用於向編譯器或 JVM 發出信號,表明必須以特殊方式處理實現此接口的類的對象,例如序列化、克隆等。
但是最近我了解到它實際上與編譯器或 JVM 無關。 例如,在Serializable
接口的情況下, ObjectOutputStream
的方法writeObject(Object)
會執行類似instanceOf Serializable
以檢測該類是否實現了Serializable
並相應地拋出NotSerializableException
。 一切都在代碼中處理,這似乎是一種設計模式,所以我認為我們可以定義我們自己的標記接口。
現在我的疑問:
上面第一點中提到的標記接口的定義是錯誤的嗎? 那么我們如何定義一個 Marker 接口呢?
而不是使用instanceOf
運算符,為什么該方法不能類似於writeObject(Serializable)
以便進行編譯時類型檢查而不是運行時?
注釋如何比標記接口更好?
writeObject(Serializable)
這樣的東西,以便進行編譯時類型檢查- 這可以讓您避免在“普通Object
”時使用標記接口的名稱污染您的代碼需要。 例如,如果您創建一個需要可序列化的類,並且具有對象成員,則您將被迫在編譯時進行強制轉換或使您的對象可Serializable
。 這很不方便,因為界面沒有任何功能。不可能在writeObject
上強制Serializable
,因為不可序列化類的子類可以序列化,但它們的實例可能會向上轉換回父類。 因此,持有對不可序列化Object
(如Object
)的引用並不意味着引用的實例不能真正序列化。 例如在
Object x = "abc";
if (x instanceof Serializable) {
}
父類( Object
)不可序列化,將使用其無參數構造函數進行初始化。 x
引用的值String
是可序列化的,條件語句將運行。
我做了一個簡單的演示來解決疑問 1 和 2:
我們將擁有由MobilePhone.java
類實現的 Movable 接口和一個不實現 Movable 接口的LandlinePhone.java
類
我們的標記界面:
package com;
public interface Movable {
}
LandLinePhone.java
和MobilePhone.java
package com;
class LandLinePhone {
// more code here
}
class MobilePhone implements Movable {
// more code here
}
我們的自定義異常類:package com;
public class NotMovableException extends Exception {
private static final long serialVersionUID = 1L;
@Override
public String getMessage() {
return "this object is not movable";
}
// more code here
}
我們的測試類: TestMArkerInterface.java
package com;
public class TestMarkerInterface {
public static void main(String[] args) throws NotMovableException {
MobilePhone mobilePhone = new MobilePhone();
LandLinePhone landLinePhone = new LandLinePhone();
TestMarkerInterface.goTravel(mobilePhone);
TestMarkerInterface.goTravel(landLinePhone);
}
public static void goTravel(Object o) throws NotMovableException {
if (!(o instanceof Movable)) {
System.out.println("you cannot use :" + o.getClass().getName() + " while travelling");
throw new NotMovableException();
}
System.out.println("you can use :" + o.getClass().getName() + " while travelling");
}}
現在當我們執行主類時:
you can use :com.MobilePhone while travelling
you cannot use :com.LandLinePhone while travelling
Exception in thread "main" com.NotMovableException: this object is not movable
at com.TestMarkerInterface.goTravel(TestMarkerInterface.java:22)
at com.TestMarkerInterface.main(TestMarkerInterface.java:14)
因此,哪個類實現了標記接口Movable
將通過測試,否則將顯示錯誤消息。
這是對Serializable 、 Cloneable等進行instanceOf
運算符檢查的方式
Java 中的標記接口是一個沒有字段或方法的接口。 更簡單地說,Java 中的空接口稱為標記接口。 標記接口的示例是
Serializable
、Cloneable
和Remote
接口。 這些用於向編譯器或 JVM 指示一些信息。 所以如果 JVM 看到一個類是Serializable
,它可以對它做一些特殊的操作。 同樣,如果 JVM 看到某個類正在實現Cloneable
,它可以執行一些操作來支持克隆。 RMI 和Remote
接口也是如此。 簡而言之,標記接口向編譯器或 JVM 指示信號或命令。
以上最初是博客文章的副本,但已針對語法進行了輕微編輯。
a/ 顧名思義,一個標記接口的存在只是為了通知任何知道它的人一個類聲明了一些東西。 任何東西都可以是Serializable
接口的 JDK 類,或者您為自定義接口編寫的任何類。
b/ 如果是標記接口,不應該暗示任何方法的存在——最好在接口中包含隱含的方法。 但是如果你知道你為什么需要它,你可以決定按照你想要的方式設計它
c/ 空接口和不使用值或參數的注解幾乎沒有區別。 但區別在於:注釋可以聲明在運行時可訪問的鍵/值列表。
它與 JVM 和編譯器無關(必然),它與任何對給定標記接口感興趣並正在測試的代碼有關。
這是一個設計決定,這樣做是有充分理由的。 請參閱 Audrius Meškauskas 的回答。
關於這個特定的話題,我認為這不是變得更好或更壞的問題。 標記界面正在做它應該做的事情。
一種。 我一直將它們視為一種設計模式,並沒有什么 JVM-Special 我在幾種情況下都使用過這種模式。
C。 我相信使用注釋來標記某些東西是比使用標記界面更好的解決方案。 僅僅因為接口首先旨在定義類型/類的通用接口。 它們是類層次結構的一部分。
注釋旨在為代碼提供元信息,我認為標記是元信息。 所以它們完全適用於那個用例。
標記接口的主要目的是創建特殊類型,其中類型本身沒有自己的行為。
public interface MarkerEntity {
}
public boolean save(Object object) throws InvalidEntityFoundException {
if(!(object instanceof MarkerEntity)) {
throw new InvalidEntityFoundException("Invalid Entity Found, can't be saved);
}
return db.save(object);
}
這里的 save 方法確保只保存實現 MarkerEntity 接口的類的對象,對於其他類型則拋出 InvalidEntityFoundException。 所以這里 MarkerEntity 標記接口定義了一個類型,為實現它的類添加特殊行為。
雖然現在也可以使用注解來標記一些特殊處理的類,但標記注解是替代命名模式而不是標記接口。
但是標記注釋不能完全取代標記接口,因為; 標記接口用於定義類型(如上所述),而標記注釋則沒有。
我首先認為 Serializable 和 Cloneable 是標記接口的壞例子。 當然,它們是與方法的接口,但它們隱含着方法,例如writeObject(ObjectOutputStream)
。 (如果您不覆蓋它,編譯器將為您創建一個writeObject(ObjectOutputStream)
方法,並且所有對象都已經具有clone()
,但編譯器將再次為您創建一個真正的clone()
方法,但有警告。兩者都這些是奇怪的邊緣情況,真的不是很好的設計示例。)
標記接口通常用於以下兩個目的之一:
1) 作為避免過長類型的捷徑,這可能發生在許多泛型中。 例如,假設你有這個方法簽名:
public void doSomething(Foobar<String, Map<String, SomethingElse<Integer, Long>>>) { ... }
打字很麻煩也很煩人,更重要的是,很難理解。 考慮一下:
public interface Widget extends Foobar<String, Map<String, SomethingElse<Integer, Long>>> { }
然后你的方法看起來像這樣:
public void doSomething(Widget widget) { ... }
不僅更清晰,而且您現在可以 Javadoc Widget 界面,而且還可以更輕松地搜索您的 Widget 代碼中的所有匹配項。
2) 標記接口也可以用來解決 Java 缺乏交叉類型的問題。 使用標記接口,您可以要求具有兩種不同類型的東西,例如在方法簽名中。 假設您的應用程序中有一些界面 Widget,就像我們上面描述的那樣。 如果您有一個方法需要一個 Widget 並且恰好讓您對其進行迭代(這是人為的,但在這里與我合作),您唯一好的解決方案是創建一個擴展兩個接口的標記接口:
public interface IterableWidget extends Iterable<String>, Widget { }
在你的代碼中:
public void doSomething(IterableWidget widget) {
for (String s : widget) { ... }
}
如果接口不包含任何方法並且通過實現該接口,如果我們的對象將獲得某種能力,則此類接口稱為標記接口。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.