我被告知Java中的Marker接口是一个空接口,用于向编译器或JVM发出信号,表明实现此接口的类的对象必须以特殊方式处理,如序列化,克隆等。 / p>
但最近我了解到它实际上与编译器或JVM无关。例如,在Serializable
接口的情况下,writeObject(Object)
的方法ObjectOutputStream
会执行instanceOf Serializable
之类的操作,以检测该类是否实现Serializable
&相应地抛出NotSerializableException
。
一切都在代码中处理,这似乎是一个设计模式,所以我认为我们可以定义自己的标记接口。
现在我怀疑:
上面提到的标记接口的定义是否错误?那么我们如何定义Marker界面呢?
而不是使用instanceOf
运算符,为什么该方法不能像writeObject(Serializable)
那样,以便有编译时类型检查而不是运行时?
注释如何比标记接口更好?
答案 0 :(得分:110)
writeObject(Serializable)
那样,以便进行编译时类型检查 - 这样可以避免污染代码当"普通Object
"时,使用标记界面的名称需要。例如,如果您创建一个需要可序列化的类并且具有对象成员,那么您将被迫在编译时进行转换或生成对象Serializable
。这很不方便,因为界面没有任何功能。答案 1 :(得分:20)
无法在Serializable
上强制执行writeObject
,因为非可序列化类的子节点可以序列化,但它们的实例可以被上传回父类。因此,保持对不可序列化的引用(如Object
)的引用并不意味着引用的实例实际上不能被序列化。例如
Object x = "abc";
if (x instanceof Serializable) {
}
父类(Object
)不可序列化,并且将使用其无参数构造函数进行初始化。 x
引用的值String
是可序列化的,条件语句将运行。
答案 2 :(得分:6)
Java中的标记接口是没有字段或方法的接口。更简单地说,Java中的空接口称为标记接口。标记接口的示例是
Serializable
,Cloneable
和Remote
接口。这些用于向编译器或JVM指示一些信息。因此,如果JVM看到类是Serializable
,它可以对它执行一些特殊操作。同样,如果JVM看到某个类正在实现Cloneable
,它可以执行一些操作来支持克隆。 RMI和Remote
接口也是如此。简而言之,标记接口指示编译器或JVM的信号或命令。
以上内容最初是a blog post的副本,但对语法进行了轻微编辑。
答案 3 :(得分:4)
a / A标记接口,因为它的名称建议只存在通知任何知道它的东西一个类声明了什么。任何东西都可以是Serializable
接口的JDK类,或者是你自定义的自定义类。
b /如果它是一个标记接口,它不应该暗示存在任何方法 - 最好在接口中包含隐含的方法。但如果你知道为什么 你需要它,你可以决定按照自己的意愿设计它
c /空接口和不使用值或参数的注释之间几乎没有区别。但不同之处在于:注释可以声明可在运行时访问的键/值列表。
答案 4 :(得分:3)
它与JVM和编译器无关(必要), 它与任何感兴趣的代码有关 测试给定的标记界面。
这是一个设计决定,这是有充分理由的。见 来自AudriusMeškauskas的回答。
关于这个特定主题,我认为这不是问题 好或坏。标记界面正在做它的事情 应该做得很好。
答案 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标记接口定义了一个类型,它为实现它的类添加了特殊的行为。
虽然注释现在也可用于标记某些特殊处理的类,但标记注释是命名模式的替代而不是标记接口。
但是标记注释不能完全取代标记接口,因为;标记接口用于定义类型(如上所述),而标记注释则不是。
答案 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)
如果接口不包含任何方法,并且通过实现该接口,如果我们的对象将获得某种能力,则这种类型的接口称为标记接口。