为什么“最终的”;不允许在Java 8接口方法?

Java 8最有用的特性之一是接口上新的default方法。它们被引入的主要原因有两个(可能还有其他原因):

从API设计人员的角度来看,我希望能够在接口方法上使用其他修饰符,例如final。这在增加方便方法时很有用,防止“意外”;在实现类时重写:

interface Sender {


// Convenience method to send an empty message
default final void send() {
send(null);
}


// Implementations should only implement this method
void send(String message);
}

如果Sender是一个类,上面已经是常见的做法:

abstract class Sender {


// Convenience method to send an empty message
final void send() {
send(null);
}


// Implementations should only implement this method
abstract void send(String message);
}

现在,defaultfinal显然是矛盾的关键字,但默认关键字本身不是严格要求的吗,所以我假设这个矛盾是故意的,以反映“类方法与body”;(只是方法)和“接口方法与本体”;(默认方法)之间的微妙差异,即我还没有理解的差异。

在某些时候,接口方法对staticfinal等修饰符的支持还没有完全探索,引用布莱恩·戈茨的话:

另一部分是我们将在多大程度上支持班级建设 接口中的工具,如final方法、私有方法、受保护 方法,静态方法等等。答案是:我们还不知道

从2011年末开始,显然,接口中增加了对static方法的支持。显然,这为JDK库本身增加了很多价值,比如Comparator.comparing()

问题:

是什么原因final(以及static final)从未出现在Java 8接口中?

44680 次浏览

很难找到和确定“这个”答案,因为@EJP的评论中提到的原因:世界上大约有2(+/- 2)人可以给出明确的答案在所有。有疑问的是,答案可能是类似于“支持最终默认方法似乎不值得重新构建内部调用解析机制”这样的东西。当然,这只是推测,但它至少有微妙的证据支持,比如下面的声明(由两个人之一)在OpenJDK邮件列表中:

我想如果允许使用“final default”方法,它们可能需要从内部调用专用重写为用户可见的调用接口。

还有一些琐碎的事实,比如当一个方法是default方法时,它就不是被认为是(真正的)final方法,就像目前在OpenJDK中的方法::is_final_method方法中实现的那样。

进一步的真正“权威”的信息确实很难找到,即使是通过过多的网络搜索和阅读提交日志。我认为这可能与解析接口方法调用时潜在的歧义有关,接口方法调用与invokeinterface指令和类方法调用对应于invokevirtual指令:对于invokevirtual指令,可能有一个简单的虚表查找,因为方法必须从超类继承,或者由类直接实现。与此相反,invokeinterface调用必须检查各自的调用站点,以找出该调用实际引用的哪一个接口(这在HotSpot Wiki的InterfaceCalls页面中有更详细的解释)。然而,final方法要么根本不被插入到虚表中,要么替换虚表中的现有项(参见invokevirtual0),类似地,默认方法替换虚表中的现有项(参见invokevirtual2)。因此,invokevirtual3原因(因此,答案)必须隐藏在(相当复杂的)方法调用解析机制的更深处,但也许这些引用仍然被认为是有帮助的,只是对于其他设法从中获得实际答案的人来说。

我不认为有必要在一个方便的接口方法上指定final,虽然我同意它是有帮助的,但似乎成本超过了收益。

无论哪种方式,您应该做的是为默认方法编写适当的javadoc,准确显示该方法是什么,不允许做什么。通过这种方式,实现接口的类“不允许”更改实现,尽管没有保证。

任何人都可以编写一个Collection,它遵循接口,然后在方法中做一些绝对违反直觉的事情,除了编写大量的单元测试之外,没有办法保护自己免受这种情况的影响。

在lambda邮件列表有很多关于它的讨论中。其中一个似乎包含了很多关于所有这些东西的讨论,如下所示:

在这个讨论中,最初的问题的作者Talden问了一些与你的问题非常相似的问题:

使所有接口成员公开的决定确实是一个错误 不幸的决定。在内部设计中使用任何界面 暴露实现的私有细节是一个大问题 如果不添加一些模糊或兼容性,这是一个很难解决的问题 打破语言的细微差别。一个兼容性的突破 规模和潜在的微妙之处会被视为不合情理 解决方案必须存在,不破坏现有的代码 是否可以重新引入'package'关键字作为访问说明符 可行的。接口中缺少说明符意味着 公共访问和类中缺少说明符意味着 包访问。哪些说明符在接口中有意义是不清楚的 -特别是为了减少开发人员的知识负担,我们必须确保访问说明符在两种情况下都是相同的 类和接口(如果它们存在)

如果没有默认方法,我会推测 接口中成员的说明符必须至少与 接口本身(因此接口实际上可以在 所有可见上下文)-使用默认方法,这是不确定的

是否有任何明确的沟通,关于这是否是一个 可能的范围内讨论?如果不是,是否应该在其他地方举行

最终布莱恩·戈茨的回答是是:

是的,这已经在探索中了。

然而,让我设定一些现实的期望——语言/虚拟机 新功能的开发需要很长时间,即使是像这样看似微不足道的功能。 现在是时候为Java SE 8提出新的语言特性了

因此,它很可能从未实现,因为它从来不是范围的一部分。它从未被及时提出以供考虑。

在另一场关于这个主题的热烈讨论中,

你已经得到了你想要的。这就是 这个特性增加了——行为的多重继承。当然我们 你要明白,人们会把它们当作特点。我们努力工作 以确保他们提供的继承模型是简单的和 足够干净,人们可以在广泛的这样做得到好的结果 各种各样的情况。与此同时,我们选择不去推动 他们超越了简单干净的工作边界 在某些情况下会导致“啊,你做得还不够”的反应。但 真的,大多数人似乎都在抱怨玻璃是 只有98%的人满了。我要拿走那98%,然后继续做!< / p >

所以这强化了我的理论,那就是它根本不在他们的设计范围之内。他们所做的是提供足够的功能来处理API进化的问题。

在某种程度上,这个问题与Java 8接口方法中不允许“同步”的原因是什么?有关

理解默认方法的关键是主要的设计目标是界面演化,而不是“把接口变成(平庸的)特征”。虽然这两者之间有一些重叠,我们试图适应后者,而不妨碍前者,但从这个角度来看,这些问题才能得到最好的理解。(还要注意,无论目的是什么,类方法将不同于接口方法,因为接口方法可以被多重继承。)

默认方法的基本思想是:它是具有默认实现的接口方法,派生类可以提供更具体的实现。因为设计中心是接口进化,所以默认方法能够以源代码兼容和二进制兼容的方式添加到接口事实之后是一个关键的设计目标。

对于“为什么不是最终的默认方法”的过于简单的回答是,这样主体就不仅仅是默认的实现,它将是唯一的实现。虽然这个答案有点太简单了,但它给了我们一个线索,这个问题已经朝着一个有问题的方向发展了。

final接口方法值得怀疑的另一个原因是它们为实现者制造了不可能的问题。例如,假设你有:

interface A {
default void foo() { ... }
}


interface B {
}


class C implements A, B {
}

在这里,一切都很好;CA继承foo()。现在假设B被更改为有一个默认的foo方法:

interface B {
default void foo() { ... }
}

现在,当我们重新编译C时,编译器会告诉我们它不知道要为foo()继承什么行为,所以C必须重写它(如果它想保留相同的行为,可以选择委托给A.super.foo())。但是如果B将其默认值设置为final,并且A不在C的作者的控制之下呢?现在C被不可挽回地破坏了;它不能在不重写foo()的情况下编译,但如果B中的foo()是final,则它不能重写。

这只是一个例子,但重点是,方法的终结性在单继承类(通常是将状态与行为结合在一起)的世界中比仅仅贡献行为并且可以多重继承的接口更有意义。很难推断“其他什么接口可能会混合到最终的实实者中”,并且允许一个接口方法是最终的可能会导致这些问题(它们不会在编写接口的人身上爆炸,而是在试图实现它的可怜用户身上爆炸)。

另一个不允许使用它们的原因是,它们的意思并不像你想象的那样。只有当类(或它的超类)没有提供方法的声明(具体的或抽象的)时,才考虑默认实现。如果默认方法是final,但超类已经实现了该方法,则默认方法将被忽略,这可能不是默认作者在将其声明为final时所期望的。(这种继承行为反映了默认方法的设计中心——接口演化。应该可以将默认方法(或现有接口方法的默认实现)添加到已经有实现的现有接口中,而不改变实现该接口的现有类的行为,确保在添加默认方法之前已经工作的类在有默认方法的情况下也能以相同的方式工作。)

当我们知道扩展interface的类可能会也可能不会override我们的实现时,我们在interface中为方法添加default关键字。但是如果我们想添加一个不希望任何实现类重写的方法呢?我们有两个选择:

  1. 添加一个default final方法。
  2. 添加一个static方法。

现在,Java说,如果我们有一个class实现了两个或多个interfaces,使得它们的default方法具有完全相同的方法名称和签名,即它们是重复的,那么我们需要在我们的类中提供该方法的实现。现在对于default final方法,我们不能提供一个实现,我们被卡住了。这就是为什么在接口中不使用final关键字。