为什么我不能在 Java 接口中定义静态方法?Java 8 允许静态接口方法接口中的静态方法重写静态方法构造函数“接口”

2022-08-31 04:19:12

编辑:从Java 8开始,接口中现在允许使用静态方法。

下面是一个示例:

public interface IXMLizable<T>
{
  static T newInstanceFromXML(Element e);
  Element toXMLElement();
}

当然,这行不通。但为什么不呢?

其中一个可能的问题是,当您拨打以下电话时会发生什么:

IXMLizable.newInstanceFromXML(e);

在这种情况下,我认为它应该只调用一个空方法(即{})。所有子类都将被迫实现静态方法,因此在调用静态方法时它们都没问题。那么,为什么这是不可能的呢?

编辑:我想我正在寻找的答案比“因为Java就是这样”更深刻。

静态方法不能被覆盖,这有什么特殊的技术原因吗?也就是说,为什么Java的设计者决定使实例方法可重写而不是静态方法?

编辑:我的设计的问题是我试图使用接口来强制执行编码约定。

也就是说,接口的目标是双重的:

  1. 我希望IXMLizable接口允许我将实现它的类转换为XML元素(使用多态性,工作正常)。

  2. 如果有人想为实现IXMLizable接口的类创建一个新的实例,他们总是会知道会有一个新的InstanceFromXML(Element e)静态构造函数。

除了在界面中放置注释之外,还有其他方法可以确保这一点吗?


答案 1

Java 8 允许静态接口方法

使用 Java 8,接口可以具有静态方法。它们还可以具有具体的实例方法,但不能具有实例字段。

这里实际上有两个问题:

  1. 为什么在糟糕的过去,接口不能包含静态方法?
  2. 为什么不能重写静态方法?

接口中的静态方法

没有强大的技术原因可以解释为什么接口在以前的版本中不能有静态方法。这可以通过重复问题的海报很好地总结出来。静态接口方法最初被认为是一个很小的语言变化,然后有一个官方建议将它们添加到Java 7中,但后来由于不可预见的复杂性而被放弃。

最后,Java 8 引入了静态接口方法,以及具有默认实现的可重写实例方法。但是,它们仍然不能具有实例字段。这些功能是 lambda 表达式支持的一部分,您可以在 JSR 335 的 H 部分中阅读有关它们的更多信息。

重写静态方法

第二个问题的答案有点复杂。

静态方法在编译时是可解析的。动态调度对于示例方法有意义,其中编译器无法确定对象的具体类型,因此无法解析要调用的方法。但是调用静态方法需要一个类,并且由于该类在编译时是静态已知的,因此不需要动态调度。

有必要了解一下实例方法的工作原理,以了解这里发生了什么。我确信实际的实现是完全不同的,但让我解释一下我对方法调度的概念,它准确地对观察到的行为进行了建模。

假设每个类都有一个哈希表,该哈希表将方法签名(名称和参数类型)映射到实际的代码块以实现该方法。当虚拟机尝试在实例上调用方法时,它会在对象中查询其类,并在类的表中查找请求的签名。如果找到方法主体,则调用该方法主体。否则,将获取该类的父类,并在其中重复查找。此过程将继续,直到找到该方法,或者不再有父类,这将导致 .NoSuchMethodError

如果超类和子类在其表中都有一个用于相同方法签名的条目,则首先会遇到子类的版本,并且永远不会使用超类的版本 - 这是一个“覆盖”。

现在,假设我们跳过对象实例,只从一个子类开始。解决方案可以按上述方式进行,为您提供一种“可重写”的静态方法。但是,解决方法都可以在编译时进行,因为编译器是从已知类开始的,而不是等到运行时才查询其类的未指定类型的对象。“重写”静态方法没有意义,因为始终可以指定包含所需版本的类。


构造函数“接口”

这里有一些材料可以解决最近对这个问题的编辑。

这听起来像是您希望为 的每个实现有效地强制使用类似构造函数的方法。忘记尝试使用接口强制执行此命令一分钟,并假装您有一些满足此要求的类。你会如何使用它?IXMLizable

class Foo implements IXMLizable<Foo> {
  public static Foo newInstanceFromXML(Element e) { ... }
}

Foo obj = Foo.newInstanceFromXML(e);

由于在“构造”新对象时必须显式命名具体类型,因此编译器可以验证它确实具有必要的工厂方法。如果没有,那又如何呢?如果我可以实现一个缺少“构造函数”的实例,并且我创建一个实例并将其传递给您的代码,那么它就是一个具有所有必要接口的接口。FooIXMLizableIXMLizable

构造是实现的一部分,而不是接口。任何成功与接口一起使用的代码都不关心构造函数。任何关心构造函数的代码都需要知道具体类型,并且可以忽略接口。


答案 2

这已经被问到并回答了,在这里

复制我的答案:

在接口中声明静态方法从来就没有意义。它们不能由正常的调用MyInterface.staticMethod()执行。如果通过指定实现类 MyImplementor.staticMethod() 来调用它们,则必须知道实际的类,因此接口是否包含它无关紧要。

更重要的是,静态方法永远不会被覆盖,如果你尝试这样做:

MyInterface var = new MyImplementingClass();
var.staticMethod();

静态规则说必须执行在声明的 var 类型中定义的方法。由于这是一个接口,这是不可能的。

您无法执行“result=MyInterface.staticMethod()”的原因是它必须执行MyInterface中定义的方法版本。但是MyInterface中不能定义一个版本,因为它是一个接口。根据定义,它没有代码。

虽然你可以说这相当于“因为Java这样做”,但实际上这个决定是其他设计决策的逻辑结果,也是有充分理由的。