为什么 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

在某些时候,对接口方法等修饰符的支持尚未得到充分探索,引用Brian Goetz的话staticfinal

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

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

问题:

是什么原因(以及)从未进入Java 8接口?finalstatic final


答案 1

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

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

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

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

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

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

interface B { 
}

class C implements A, B { 
}

在这里,一切都很好; 继承自 。现在假设将更改为具有一个方法,默认值为:Cfoo()ABfoo

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

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

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

禁止它们的另一个原因是它们并不意味着你认为它们意味着什么。仅当类(或其超类)不提供方法的声明(具体或抽象)时,才考虑默认实现。如果默认方法是最终方法,但超类已经实现了该方法,则将忽略默认值,这可能不是默认作者在声明其最终方法时所期望的。(此继承行为反映了默认方法的设计中心 - 接口演变。应该可以将默认方法(或现有接口方法的默认实现)添加到已有实现的现有接口,而无需更改实现该接口的现有类的行为,从而保证在添加默认方法之前已工作的类在存在默认方法的情况下将以相同的方式工作。


答案 2

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

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

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

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

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

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

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

最终,Brian Goetz的回答是:

是的,这已经在探索中。

但是,让我设定一些现实的期望 - 语言/ VM功能有很长的交付周期,即使是像这样的微不足道的。为Java SE 8提出新的语言特性想法的时间几乎已经过去了。

因此,很可能它从未实现过,因为它从未成为范围的一部分。它从未及时提出供审议。

在另一次关于最终辩护方法的激烈讨论中,布莱恩再次说

你已经得到了你所希望的。这正是这个功能所添加的 - 行为的多重继承。当然,我们知道人们会把它们当作特质。我们一直在努力确保他们提供的继承模型足够简单和干净,人们可以在各种情况下获得良好的结果。与此同时,我们选择不把它们推到简单干净利落的界限之外,这在某些情况下会导致“哇,你走得不够远”的反应。但实际上,这个线程的大部分似乎都在抱怨玻璃杯只有98%的满。我会接受这98%并继续下去!

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


推荐