我认为不能,因为标记接口原则是没有任何方法,但由于默认方法不是抽象的,我不确定。
4 回答
就 Java 而言,“标记”接口只是一个常规接口。因此,它可以像任何 (Java-8) 接口一样具有默认方法。
现在,至于这是否违反了 Marker 接口的原则,我不得不说是的。Marker 接口应该充当各种标志,仅标识一个类满足某些外部标准。现在,它可以是一个 Marker 接口并具有抽象/默认方法,但它不再完全符合定义。
来自Effective Java(第二版):
标记接口是不包含方法声明的接口,而只是指定(或“标记”)实现该接口的类具有某些属性。
标记接口是一种设计模式,因此我们可以通过观察定义来回答您的问题:
在 Java 的早期版本中,标记接口是声明类元数据的唯一方法。例如,Serializable Marker Interface 让类的作者说他们的类在序列化和反序列化时会正确运行。
在 Java 的上下文中,Marker 接口的目的是说明该类。有趣的是,它把它标记为某事。接口就是一个例子Serializable
,它什么也没做,只是标记了 aClass
能够被序列化为 a String
。这里的问题是:
定义是否包括功能?
不,我认为不会。功能不仅仅是关于类的元数据;它有助于定义类本身。它从元数据到数据迈出了这一步。所以就设计模式而言,标记接口不能定义或声明功能;它可以简单地对实施作出声明Class
。
尽管@Azar的回答是正确的,但我们不能忘记,Effective Java 是在引入默认方法之前编写的。
什么是标记界面?
有两种查看标记接口的方法:
- 它们是不声明任何方法的接口。
- 它们是不强制实现任何方法的接口。
“官方”定义是第一个,但在 Java 7 之前,这两个语句是等价的。这是 Effective Java 中反复出现的模式,一旦您发布了一个接口,就不能向它添加任何方法,因为它会强制实现新方法。
然而,这正是默认方法试图解决的问题:允许接口的演变,而不需要改造所有实现它们的类。这也使得上述两个语句的含义略有不同:默认方法明显违反了语句 1,并且设计上不违反语句 2。
这一切在实践中意味着什么?
想象一下,您编写了一个 XML 序列化引擎并创建了一个标记接口XmlSerializable
来配合它:
public interface XmlSerializable {}
到目前为止,一切都很好。但后来你意识到你实际上有一些需要特殊处理的类,它们需要提供自己的自定义转换器。所以你可以做的是这样的:
public interface XmlSerializable {
public static final Map<Class,Class> CONVERTERS = ...
default Class customConverter() {
return CONVERTERS.get(this.getClass());
}
}
那会不再XmlSerializable
是标记界面吗?你可以说它仍然是一个标记接口,因为你并没有真正直接向你的接口添加额外的行为,只有额外的元数据会影响序列化引擎的行为。另一方面,这个解决方案允许实现类来覆盖customConverter()
,这有点狡猾,标记接口不应该允许这样做。(再说一次,Serializable
依赖Cloneable
实现类中的“魔术”方法更好吗?我不这么认为。)
可以说上面的例子不是解决这类问题的好方法,使用注释可能会好得多。但这也适用于大多数“真正的”标记界面。
tl;博士
我们可以得出结论,只有默认方法的接口或多或少等同于空接口。如果你想在理论上做一个区分,而不是把它称为标记接口,那当然没问题。但是实际上几乎没有区别,而且考虑到标记接口的固有问题,我们无论如何都应该避免它们。
标记接口可以具有默认方法,但拥有它们是荒谬的。
标记界面与传统界面的使用方式不同。常规接口定义方法,包括抽象方法和默认方法。因此,程序以该接口作为其类型声明变量并通过该接口类型的引用调用这两种方法是明智的。
相比之下,标记接口不用于调用方法。它是关于通过类型系统声明的对象的一条元信息。它通常通过instanceof
表达式调用代码或偶尔使用Class.isAssignableFrom()
。声明一个类型为标记接口的变量是没有意义的,因为你不能对这样的变量做任何事情。
JDK 中的标记接口示例有Cloneable
、RandomAccess
和Serializable
.
现在考虑为某些标记接口添加默认方法:
interface Marker {
default void foo() { ... }
}
默认实现可以foo
做什么?
默认方法的实现通常希望在 上进行操作this
,并且它们通过在 上调用其他实例方法来实现this
。他们可以调用其他默认方法,但是让一堆默认方法互相调用是没有用的。this
最终,必须执行某种实际操作。由于接口方法无法访问状态(字段),因此任何实际操作都必须由驻留在实现类中的抽象方法实现来执行。但是,在标记界面中没有这样的方法。
的默认实现foo
可以在此接口或其他某个类上调用静态方法。这几乎是毫无意义的,因为首先将这种方法表示为静态方法可能会更好。实现可以传递this
给静态方法,但该方法不能对这样的引用做任何有用的事情,因为它没有方法!好吧,它可能有默认方法,但现在我们要绕圈子了。
为了使默认方法在接口上有用,该接口也需要具有抽象方法。但是如果它有抽象方法,它就不再是一个标记接口。因此,在标记接口上使用默认方法是没有意义的。