Java 中的标记接口 Marker Interfaces? ?

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

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

现在我怀疑:

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

  2. 为什么不使用类似于 writeObject(Serializable)的方法来代替使用 instanceOf操作符,这样就有了编译时类型检查而不是运行时类型检查?

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

99915 次浏览

不可能在 writeObject上强制执行 Serializable,因为不可序列化类的子类可以是可序列化的,但是它们的实例可以被上溯回父类。因此,保留对不可序列化(如 Object)的引用并不意味着所引用的实例不能被真正序列化。比如说

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

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

  1. 它与 JVM 和编译器(必然)没有任何关系, 它与任何感兴趣的代码都有关系 测试给定的标记接口

  2. 这是一个设计的决定,而且它是为了一个很好的理由而做的 来自 Audrius Me Kauskas 的回答

  3. 关于这个话题,我不认为这是一个问题 标记接口正在做它应该做的事情 应该没问题的

  1. 上面第一点提到的标记接口的定义是错误的吗?-(1)标记接口必须为空,(2)实现它意味着对实现类进行一些特殊处理。不正确的地方在于,它意味着 JVM 或编译器会以不同的方式对待这个类的对象: 您正确地观察到,正是 Java 类库的代码将这些对象视为可克隆的、可序列化的,等等。它与编译器或 JVM 没有任何关系。
  2. 而不是使用 instanceOf 操作符为什么不能使用类似于 writeObject(Serializable)的方法,这样就有一个编译时类型检查 -当需要“纯 Object”时,这样可以避免使用标记接口的名称来污染代码。例如,如果您创建了一个需要可序列化且具有对象成员的类,那么您将被迫在编译时进行强制转换或创建对象 Serializable。这是不方便的,因为界面没有任何功能。
  3. 注释如何优于标记接口?-它们允许您实现同样的目的,即将关于类的元数据传递给其使用者,而不需要为其创建单独的类型。注释也更加强大,让程序员可以将更复杂的信息传递给“使用”它的类。

一。 我一直把它们看作是一种设计模式,没有什么 JVM-Special,我曾在几种情况下使用过这种模式。

C. 我相信使用注释来标记一些东西是一个比使用标记接口更好的解决方案。仅仅是因为接口的首要目的是定义 Type/Class 的公共接口。他们是阶级等级的一部分。

注释旨在为代码提供元信息,我认为标记就是元信息。所以它们正好适用于那个用例。

A/A 标记接口作为它的名字建议存在只是为了通知 任何知道这件事的人一个类声明的东西。任何东西都可以是用于 Serializable接口的 JDK 类,或者您为自定义接口编写的任何类。

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

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

首先,我认为 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) { ... }

它不仅更清晰,而且您现在可以对 Widget 接口进行 Javadoc,并且在 Widget 代码中搜索所有出现的情况也更加容易。

2)标记接口也可以用来解决 Java 缺乏交集类型的问题。使用标记接口,您可以要求某些内容具有两种不同的类型,例如在方法签名中。假设您的应用程序中有一些接口小部件,就像我们上面描述的那样。如果你有一个需要 Widget 的方法,并且恰好允许你在它上面进行迭代(这是人为的,但是在这里和我一起工作) ,你唯一好的解决方案是创建一个扩展两个接口的标记接口:

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

在你的代码里:

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

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

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

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

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

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

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

标记接口注释的来源

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

我做了一个简单的示范来解决第一和第二个疑问:

我们将有可移动的接口,将实现由 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
}

我们的自定义例外类: 包装;

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");
}}

现在执行 main class:

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的类都将通过测试,否则将显示错误消息。

这是对 可序列化可以克隆等进行 instanceOf操作员检查的方法