Java中的标记接口?


134

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

但是最近我了解到它实际上与编译器或JVM无关。例如,在使用Serializable接口的情况下,的方法writeObject(Object)ObjectOutputStream执行类似的操作instanceOf Serializable来检测类是否相应地实现Serializable和抛出NotSerializableException。一切都在代码中处理,这似乎是一种设计模式,所以我认为我们可以定义自己的标记接口。

现在我的疑问:

  1. 上面第一点提到的标记接口定义错误吗?那么我们如何定义Marker接口呢?

  2. 并且instanceOf为什么不使用运算符而不是使用运算符,所以为什么要使用writeObject(Serializable)编译时类型检查而不是运行时呢?

  3. 注释比标记接口更好吗?


8
Serializable因为注释是胡说八道,@NonNull而接口则是胡说八道。我会说:注释是标记+元数据。顺便说一句:Annotations的先驱者是XDoclet,他出生于Javadoc,被Annotations杀死。
Grim 2014年

Answers:


117
  1. 上面第一点提到的标记接口的定义是否错误?-在以下部分中是正确的:(1)标记接口必须为空,并且(2)实现该接口意味着要对实现类进行某些特殊处理。不正确的部分是,这意味着JVM或编译器将以不同的方式对待该类的对象:您正确地观察到Java类库的代码将这些对象视为可克隆,可序列化等。与编译器或JVM无关。
  2. 而不是使用instanceOf运算符,为什么该方法不能这样,writeObject(Serializable)以至于进行编译时类型检查 -这样可以避免在需要“纯文本Object” 时用标记接口的名称污染代码。例如,如果您创建一个需要可序列化并且具有对象成员的类,那么您将不得不进行强制转换或Serializable在编译时创建对象。这很不方便,因为该界面没有任何功能。
  3. 注释比标记接口如何好?-它们使您达到了将有关类的元数据传达给其使用者的相同目的,而无需为其创建单独的类型。注释也更强大,它使程序员可以将更复杂的信息传递给“消费”它的类。

14
我一直了解的方式是,注释是一种“标记接口2.0”:自Java 1.1开始存在可序列化,注释是在1.5中添加的
-blagae

并且因为writeObject是私有的,这意味着,例如,一个类不必调用超类实现
棘手的

15
要记住的一件事是,在最初设计标准库后的几年里,注释已添加到该语言中。如果注解从一开始就是用该语言编写的,那么可疑的是Serializable是否会是一个接口,它可能会注解。
Theodore Norvell

好吧,在这种情况下,Cloneable尚不清楚更改的是实例的库还是JVM处理。
Holger 2014年

“ [...没有为其创建单独的类型。” -我要说的是正是它们之间的区别:标记接口确实引入了类型,而注释(种类)却没有。
aioobe

22

不可强制执行SerializablewriteObject因为不可序列化类的子级可以序列化,但是它们的实例可以向上转换回父级。因此,持有对无法序列化的内容(例如Object)的引用并不意味着所引用的实例不能真正被序列化。例如在

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

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


6

Java中的标记接口是没有字段或方法的接口。简而言之,Java中的空接口称为标记接口。的标记接口的例子是SerializableCloneableRemote接口。这些用于向编译器或JVM指示一些信息。因此,如果JVM看到一个类是Serializable,它可以对其进行一些特殊的操作。同样,如果JVM看到正在实现某些类Cloneable,则它可以执行一些操作以支持克隆。RMI和Remote接口也是如此。简而言之,标记器接口向编译器或JVM指示信号或命令。

上面的内容最初是一篇博客文章的副本,但经过了语法上的少量编辑。


6
可以复制,但也请提及源:javarevisited.blogspot.com/2012/01/…。如果您也不要复制粘贴拼写错误,那也很好。:)
Saurabh Patil 2015年

6

我做了一个简单的演示来解决第1个和第2个问题:

我们将具有可调式界面,将通过实施MobilePhone.java分类和多一个类LandlinePhone.java里面做实现可移动的接口

我们的标记界面:

package com;

public interface Movable {

}

LandLinePhone.javaMobilePhone.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将通过测试,否则将显示错误消息。

这是instanceOfSerializableCloneable等进行操作员检查的方式


5

a /标记接口,顾名思义,仅存在是为了通知知道它的任何人一个类声明了某些东西。任何东西都可以是Serializable接口的JDK类,也可以是您为自定义类编写的任何类。

b /如果它是标记器接口,则不应暗示存在任何方法-最好在接口中包含隐含方法。但是,如果您知道自己为什么 这么做,可以决定根据需要进行设计需要它

c /空接口和不使用值或参数的注释之间几乎没有区别。但区别在于:注释可以声明键/值的列表,这些键/值将在运行时访问。


3

一个。我一直将它们视为一种设计模式,没有在某些情况下使用JVM-Special的方式。

C。我相信,使用注释来标记某些东西比使用标记接口更好。仅仅因为接口首先旨在定义类型/类的公共接口。它们是阶层的一部分。

注释旨在向代码提供元信息,我认为标记是元信息。因此,它们恰好适合该用例。


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

  2. 这是一个设计决定,并且有充分的理由。请参阅AudriusMeškauskas的答案。

  3. 关于这个特定主题,我认为这不是变好还是变坏。标记器接口正在做应该做的事情。


您可以添加链接到“ AudriusMeškauskas的答案”吗?我在此页面上看不到具有该名称的任何内容。
萨拉·梅瑟

2

标记接口的主要目的是创建特殊的类型,其中类型本身没有自身的行为。

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标记接口定义了一个类型,该类型向实现它的类添加特殊行为。

尽管现在也可以使用注释来标记类以进行某些特殊处理,但是标记注释可以替换命名模式,而不是标记接口。

但是标记注释不能完全替代标记接口,因为;标记接口用于定义类型(如上所述),而标记注释不定义。

标记界面注释的来源


1
+1表示它实际上只是程序员必须明确声明的可以用作示例数据库的“安全”钩子。
Ludvig W

1

我首先要争辩说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的方法,而该Widget也恰巧允许您对其进行迭代(它是人为设计的,但请在此处与我合作),那么您唯一的好解决方案是创建一个扩展两个接口的标记接口:

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

在您的代码中:

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

1
编译器并没有,如果你的类实现创建的任何方法SerializableCloneable。您可以通过检查类文件来验证它。此外,创建“快捷界面” 并不是标记界面的目的。这确实是一种糟糕的编码实践,因为它需要创建其他实现类来实现其他接口。顺便说一下,Java 已经有十年的交集类型了。了解泛型…
Holger 2014年

对于可克隆,您是对的。它确实更改了Object.clone()的功能。还是很恐怖。请参见Josh Bloch 链接。 快捷接口不一定是一种好的设计模式,因为您可以任意限制可以发送给方法的内容。同时,我确实认为编写更清晰,甚至更具限制性的代码通常是一个合理的权衡。至于交集类型,这仅适用于泛型,并且是不可表示的,因此在许多情况下是无用的。尝试使用既可序列化又可用于其他任何东西的实例变量。
MikeyB 2014年

0

如果接口不包含任何方法,并且通过实现该接口(如果我们的对象将获得某种能力),则此类接口称为标记接口。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.