Java中的标记接口?

我被告知,Java中的Marker接口是一个空的接口,用于向编译器或JVM发出信号,告知实现此接口的类的对象必须以特殊方式处理,如序列化,克隆等。

但最近我了解到,它实际上与编译器或JVM无关。 例如,在Serializable接口的情况下, ObjectOutputStream writeObject(Object)方法可以像instanceOf Serializable那样检测类是否实现了Serializable并相应地抛出NotSerializableExceptionexception。 一切都在代码中处理,这似乎是一个devise模式,所以我认为我们可以定义我们自己的标记接口。

现在我怀疑:

  1. 上面第一点提到的标记界面的定义是错误的吗? 那么我们怎样才能定义一个标记界面呢?

  2. 而不是使用instanceOf操作符为什么不能像writeObject(Serializable)这样的东西,所以有一个编译时types检查,而不是运行时?

  3. 注释如何比标记界面更好?

  1. 上面第一点提到的标记界面的定义是错误的吗? – (1)标记接口必须是空的,(2)实现它意味着对实现类进行一些特殊的处理。 不正确的地方在于它意味着JVM或者编译器会以不同的方式处理这个类的对象:在观察它是将这些对象视为可复制,可序列化等的Java类库的代码时是正确的。与编译器或JVM无关。
  2. 而不是使用instanceOf操作符为什么不能像writeObject(Serializable)这样的东西,所以有一个编译时types检查 – 这可以让你避免污染你的代码与标记接口的名称时,“普通Object ”是必要的。 例如,如果你创build一个需要可序列化的类,并且拥有对象成员,那么你将不得不在编译时进行转换或使对象成为Serializable 。 这是不方便的,因为界面没有任何function。
  3. 标注如何比标记界面更好? – 它们让你实现向用户传递有关类的元数据的同样目的,而不需要为它创build一个单独的types。 注释也更加强大,让程序员将更复杂的信息传递给“消耗”它的类。

writeObject上强制Serializable是不可能的,因为不可序列化的类的子类可以是可序列化的,但是它们的实例可能会被上传回父类。 因此,持有对不可序列化(如Object )的引用并不意味着被引用的实例实际上不能被序列化。 例如在

  Object x = "abc"; if (x instanceof Serializable) { } 

父类( Object )不可序列化,并将使用其无参数构造函数进行初始化。 由xString引用的值是可序列化的,条件语句将运行。

a / A标记接口,因为它的名字build议只存在通知任何知道它的类声明的东西。 任何东西都可以是用于Serializable接口的JDK类,或者是任何为自定义类写你自己的类。

b /如果它是一个标记接口,它不应该暗示任何方法的存在 – 最好是在接口中包含隐含的方法。 但是,如果你知道为什么需要它,你可以决定devise它

c /空白界面和不使用值或参数的注释之间几乎没有区别。 但区别在于:注释可以声明一个在运行时可访问的键/值列表。

一个。 我一直把它们看作是一种devise模式,没有任何JVM-Special在几种情况下使用过这种模式。

C。 我相信使用注释来标记是一个更好的解决scheme,然后使用标记接口。 只是因为接口首先是为了定义types/类的通用接口。 他们是class级的一部分。

注释旨在为代码提供元信息,我认为这个标记是元信息。 所以他们正是这个用例。

  1. 它与JVM和编译器无关(必然),它与任何感兴趣的代码有关,并正在testing给定的标记接口。

  2. 这是一个devise决定,这是完成了一个很好的理由。 看到AudriusMeškauskas的答案。

  3. 关于这个特定的话题,我不认为这是一个好或坏的问题。 标记界面正在做它应该做的很好。

标记接口的主要目的是创buildtypes本身没有自己行为的特殊types。

 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接口的类的对象,对于其他types则引发InvalidEntityFoundException。 所以在这里MarkerEntity标记接口定义了一个类来为实现它的类添加特殊的行为。

虽然注释现在也可以用来标记类的一些特殊的处理,但标记注释是命名模式而不是标记接口的替代。

但标记注释不能完全替代标记界面,因为; 标记接口用于定义types(如上面已经解释过的那样),而标记注释则没有。

标记界面评论的来源

我会首先论证Serializable和Cloneable是标记接口的不好的例子。 当然,它们是与方法的接口,但它们暗示着诸如writeObject(ObjectOutputStream) 。 (如果你不覆盖它,编译器会为你创build一个writeObject(ObjectOutputStream)方法,并且所有的对象都已经有了clone() ,但编译器会再次为你创build一个真正的clone()方法,但是有注意事项。这些都是奇怪的边缘情况,真的不是好的devise例子。)

标记接口通常用于以下两个目的之一:

1)作为一个避免过长的types的快捷方式,可能会出现很多generics。 例如,假设你有这个方法签名:

 public void doSomething(Foobar<String, Map<String, SomethingElse<Integer, Long>>>) { ... } 

这是混乱和烦人的types,更重要的是难以理解。 考虑这个,而不是:

 public interface Widget extends Foobar<String, Map<String, SomethingElse<Integer, Long>>> { } 

那么你的方法看起来像这样:

 public void doSomething(Widget widget) { ... } 

不仅更清晰,而且现在可以使用Javadoc的Widget接口,并且在您的Widget代码中search所有的事件也更容易。

2)标记接口也可以用来解决Java缺乏交集types的问题。 使用标记接口,您可以要求某种东西是两种不同的types,例如在方法签名中。 假设你的应用程序中有一些接口Widget,就像我们上面所描述的那样。 如果你有一个方法,需要一个Widget,也可以让你迭代它(这是人为的,但在这里与我一起工作),你唯一的好的解决scheme是创build一个标记接口,扩展两个接口:

 public interface IterableWidget extends Iterable<String>, Widget { } 

在你的代码中:

 public void doSomething(IterableWidget widget) { for (String s : widget) { ... } } 

Java中的标记接口是没有字段或方法的接口。 简而言之,java中的空接口被称为标记接口。 标记接口的例子是Serializable,Cloneable和Remote接口。 这些用于向编译器或JVM指示某些内容。 因此,如果JVM看到一个Class实现标记接口,它会对它执行一些特殊的操作,类似的方式,如果JVM看到一个类是实现Cloneable它执行一些操作来支持克隆。 RMI和Remote接口也是如此。 所以简而言之,Marker接口是指示或者信号或者命令给编译器或者JVM。

如果一个接口不包含任何方法,并且如果我们的对象会获得某种能力,那么通过这个接口来实现这种接口就称为标记接口。