为什么 Java 8 接口方法中不允许使用“final”?

新手上路,请多包涵

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 年底的那个时候开始,很明显,接口中添加了对 static 方法的支持。显然,这为 JDK 库本身增加了很多价值,例如 Comparator.comparing()

问题:

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

原文由 Lukas Eder 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 616
2 个回答

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

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

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

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

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

 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 has override-d它(并且可以选择委托给 A.super.foo() 如果它想保留相同的行为。)但是如果 B A 设为默认值 final --- 不受 C 作者的控制?现在 C 已无法挽回地损坏;它不能在不覆盖的情况下编译 foo() ,但它不能覆盖 foo() 如果它是最终的 B

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

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

原文由 Brian Goetz 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

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

如果不向该语言添加一些晦涩或破坏兼容性的细微差别,则很难修复它。如此严重的兼容性破坏和潜在的微妙之处将被视为不合情理,因此必须存在不破坏现有代码的解决方案。

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

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

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

最终 Brian Goetz 的回答 是:

是的,这已经在探索中。

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

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

在关于该主题的 最终防御者方法 的另一场激烈讨论中, Brian 再次说道

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

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

原文由 Edwin Dalorzo 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题