Java中的标记接口?

时间:2014-09-15 14:24:05

标签: java design-patterns marker-interfaces

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

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

现在我怀疑:

  1. 上面提到的标记接口的定义是否错误?那么我们如何定义Marker界面呢?

  2. 而不是使用instanceOf运算符,为什么该方法不能像writeObject(Serializable)那样,以便有编译时类型检查而不是运行时?

    < / LI>
  3. 注释如何比标记接口更好?

10 个答案:

答案 0 :(得分:110)

  1. 上面第1点中提到的标记接口的定义是错误的吗? - 在(1)标记接口必须为空,(2)实现它的部分中是正确的暗示对实施阶级的一些特殊待遇。不正确的部分是它暗示JVM或编译器会以不同的方式处理该类的对象:您在观察它是Java类库的代码是正确的,它将这些对象视为可克隆,可序列化等。它具有与编译器或JVM无关。
  2. 而不是使用instanceOf运算符,为什么该方法可以像writeObject(Serializable)那样,以便进行编译时类型检查 - 这样可以避免污染代码当&#34;普通Object&#34;时,使用标记界面的名称需要。例如,如果您创建一个需要可序列化的类并且具有对象成员,那么您将被迫在编译时进行转换或生成对象Serializable。这很不方便,因为界面没有任何功能。
  3. 注释如何优于标记界面? - 它们可以实现将类的元数据传递给其使用者的相同目的,而无需为其创建单独的类型。注释也更强大,让程序员将更复杂的信息传递给消费&#34;它。

答案 1 :(得分:20)

无法在Serializable上强制执行writeObject,因为非可序列化类的子节点可以序列化,但它们的实例可以被上传回父类。因此,保持对不可序列化的引用(如Object)的引用并不意味着引用的实例实际上不能被序列化。例如

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

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

答案 2 :(得分:6)

  

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

以上内容最初是a blog post的副本,但对语法进行了轻微编辑。

答案 3 :(得分:4)

a / A标记接口,因为它的名称建议只存在通知任何知道它的东西一个类声明了什么。任何东西都可以是Serializable接口的JDK类,或者是你自定义的自定义类。

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

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

答案 4 :(得分:3)

  1. 它与JVM和编译器无关(必要), 它与任何感兴趣的代码有关 测试给定的标记界面。

  2. 这是一个设计决定,这是有充分理由的。见 来自AudriusMeškauskas的回答。

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

答案 5 :(得分:3)

一个。 我一直把它们视为一种设计模式,没有JVM-Special我在几种情况下都使用过该模式。

℃。 我相信使用Annotations来标记某些东西是比使用标记界面更好的解决方案。仅仅因为接口首先是为了定义类型/类的通用接口。他们是阶级层次的一部分。

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

答案 6 :(得分: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标记接口定义了一个类型,它为实现它的类添加了特殊的行为。

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

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

Source for marker interface comment

答案 7 :(得分:2)

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

我们将拥有Movable接口,该接口将由MobilePhone.java类实现,而另外一个LandlandPhone.java类将实现实现Movable接口

我们的标记界面:

package com;

public interface Movable {

}

LandLinePhone.java和MobilePhone.java

 package com;

 class LandLinePhone {
    // more code here
 }
 class MobilePhone implements Movable {
    // more code here
 }

我们的自定义异常类:      打包com;

公共类NotMovableException扩展了异常{

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运算符检查的方式

答案 8 :(得分: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也可以让你迭代它(它是人为的,但在这里与我一起工作),你唯一的好解决方案是创建一个扩展两个接口的标记接口:

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

在您的代码中:

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

答案 9 :(得分:0)

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