363

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

现在,defaultandfinal显然是矛盾的关键字,但 default 关键字本身并不是严格要求的,所以我假设这种矛盾是故意的,以反映“带主体的类方法”(只是方法)和“接口”之间的细微差别带有主体的方法”(默认方法),即我尚未理解的差异。

在某些时候,还没有完全探索对接口方法等修饰符的支持,static引用Brian Goetz的话:final

另一部分是我们将在接口中支持类构建工具的程度,例如最终方法,私有方法,受保护方法,静态方法等。答案是:我们还不知道

自 2011 年底以来,显然static增加了对接口方法的支持。显然,这为 JDK 库本身增加了很多价值,例如Comparator.comparing().

问题:

是什么原因final(也是static final)从未进入 Java 8 接口?

4

5 回答 5

460

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

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

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

“为什么不是最终的默认方法”的过于简单的答案是,那么主体将不仅仅是默认实现,它将是唯一的实现。虽然这个答案有点过于简单,但它为我们提供了一个线索,即这个问题已经朝着一个值得怀疑的方向发展。

最终接口方法有问题的另一个原因是它们给实现者带来了不可能的问题。例如,假设您有:

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

interface B { 
}

class C implements A, B { 
}

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

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

现在,当我们去重新编译时C,编译器会告诉我们它不知道要继承什么行为foo(),所以必须重写它(如果它想保留相同的行为C,可以选择委托给它。)但是如果已经默认了,是不是在作者的控制之下?现在已经无可挽回地破碎了;如果不覆盖它就无法编译,但如果它是 final in则无法覆盖。 A.super.foo()BfinalACCfoo()foo()B

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

不允许它们的另一个原因是它们不会像您认为它们的意思一样。仅当类(或其超类)不提供方法的声明(具体或抽象)时才考虑默认实现。如果默认方法是最终方法,但超类已经实现了该方法,则默认方法将被忽略,这可能不是默认作者在声明它为最终方法时所期望的。(这种继承行为反映了默认方法的设计中心——接口进化。应该可以将默认方法(或现有接口方法的默认实现)添加到已有实现的现有接口,而无需更改实现接口的现有类的行为,

于 2014-05-05T16:10:26.823 回答
45

在 lambda 邮件列表中有很多关于它的讨论。其中一个似乎包含很多关于所有这些东西的讨论如下:关于各种接口方法可见性(是最终防御者)

在这次讨论中,原始问题的作者 Talden提出了与您的问题非常相似的问题:

将所有界面成员公开的决定确实是一个不幸的决定。在内部设计中对接口的任何使用都会暴露实现的私有细节,这是一个很大的问题。

在不给语言添加一些晦涩难懂或破坏兼容性的细微差别的情况下,这是一个很难解决的问题。这种程度的兼容性中断和潜在的微妙之处将被视为不合情理,因此必须存在一个不会破坏现有代码的解决方案。

重新引入“包”关键字作为访问说明符是否可行。接口中没有说明符意味着公共访问,而类中没有说明符意味着包访问。哪些说明符在接口中有意义尚不清楚 - 特别是如果为了尽量减少开发人员的知识负担,我们必须确保访问说明符在类和接口中的含义相同(如果它们存在)。

在没有默认方法的情况下,我推测接口中成员的说明符必须至少与接口本身一样可见(因此该接口实际上可以在所有可见上下文中实现) - 默认方法不是如此确定。

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

最终, Brian Goetz 的回答是:

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

然而,让我设定一些现实的期望——语言/虚拟机特性有很长的交付周期,即使是像这样看似微不足道的特性。为 Java SE 8 提出新的语言特性想法的时间已经过去了。

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

在另一个关于该主题的最终防守方法的激烈讨论中,布赖恩再次说

你已经得到了你想要的。这正是这个特性所增加的——行为的多重继承。当然,我们知道人们会将它们用作特征。我们一直在努力确保他们提供的继承模型足够简单和干净,以便人们在各种情况下都能获得良好的结果。同时,我们选择不将它们推到简单而干净的工作范围之外,这在某些情况下会导致“哦,你做得不够远”的反应。但实际上,这个帖子的大部分似乎都在抱怨玻璃杯只有 98% 满了。我会接受 98% 并继续努力!

因此,这强化了我的理论,即它根本不是他们设计的范围或一部分的一部分。他们所做的是提供足够的功能来处理 API 演进的问题。

于 2014-05-04T15:46:33.970 回答
18

对于@EJP 的评论中提到的原因,很难找到和确定“THE”的答案:世界上大约有 2 (+/- 2) 人可以给出明确的答案毫无疑问,答案可能只是“支持最终默认方法似乎不值得为重构内部调用解决机制而付出努力”。这当然是猜测,但至少有微妙的证据支持,例如OpenJDK 邮件列表中的这个声明(由两人之一)

“我想如果允许“最终默认”方法,它们可能需要从内部调用特殊重写为用户可见的调用接口。”

像这样的琐碎事实,当一个方法是一个方法时,它根本不被认为是一个(真正的)最终方法default,正如当前在OpenJDK中的Method::is_final_method方法中实现的那样。

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

于 2014-05-04T12:12:40.980 回答
4

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

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

任何人都可以编写一个Collection遵循接口的程序,然后用绝对违反直觉的方法来做事,除了编写大量的单元测试之外,没有办法让自己免受这种影响。

于 2014-05-04T12:36:53.690 回答
0

当我们知道扩展我们实现的类可能或可能不是我们的实现时,我们将default关键字添加到我们的方法中。但是如果我们想添加一个我们不希望任何实现类覆盖的方法呢?好吧,我们有两个选择:interfaceinterfaceoverride

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

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

于 2020-06-03T16:37:23.817 回答