ChatGPT解决这个技术问题 Extra ChatGPT

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

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

提供实际的默认实现。示例:Iterator.remove()

允许 JDK API 进化。示例:Iterable.forEach()

从 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显然是矛盾的关键字,但默认关键字本身是would not have been strictly required,所以我假设这种矛盾是故意的,以反映“带主体的类方法”之间的细微差别< /em>(只是方法)和“与主体的接口方法”(默认方法),即我尚未理解的差异。

在某个时间点,尚未完全探索对接口方法上的 staticfinal 等修饰符的支持,citing Brian Goetz

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

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

问题:

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

我很抱歉成为一个湿毯子,但标题中表达的问题要在 SO 的条款内得到回答的唯一方法是通过 Brian Goetz 或 JSR 专家组的引用。我了解 BG 已要求进行公开讨论,但这恰恰违反了 SO 的条款,因为它“主要基于意见”。在我看来,责任在这里被推卸了。专家组和更广泛的 Java 社区进程的工作是激发讨论并提出基本原理。不是这样的。所以我投票以“主要基于意见”的方式结束。
众所周知,final 防止方法被覆盖,并且看到您必须如何覆盖从接口继承的方法,我不明白为什么将其设为 final 是有意义的。除非它表明该方法在覆盖它一次之后是最终的..在那种情况下,也许有困难?如果我不理解这一点,请让我知道。看起来很有趣
@EJP:“如果我能做到这一点,你也能做到,并回答你自己的问题,因此不需要问。”这几乎适用于这个论坛上的所有问题,对吧?我总是可以自己用 5 个小时谷歌某个主题,然后学习其他人必须以同样困难的方式学习的东西。或者我们再等几分钟,让某人给出一个更好的答案,让未来的每个人(包括到目前为止的 12 个赞成票和 8 个星)都可以从中受益,因为 SO 在 Google 上得到了很好的引用。所以,是的。这个问题可以完美地融入 SO 的问答形式。
@VinceEmigh“...看看你必须如何覆盖从接口继承的方法...”这在 Java 8 中是不正确的。Java 8 允许你在接口中实现方法,这意味着你不需要'不必在实现类中实现它们。在这里,final 将用于防止实现类覆盖接口方法的默认实现。
@EJP 你永远不知道:Brian Goetz may reply

B
Brian Goetz

这个问题在某种程度上与What is the reason why “synchronized” is not allowed in Java 8 interface methods?有关

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

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

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

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

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()

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

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


很高兴看到您回答有关新语言功能的问题!在弄清楚我们应该如何使用新功能时,弄清设计的意图和细节非常有帮助。参与设计的其他人是否为 SO 做出了贡献——或者你只是自己做这件事?我会在 java-8 标签下关注你的答案——我想知道是否有其他人也在做同样的事情,所以我也可以关注他们。
@Shorn Stuart Marks 已在 java-8 标记中处于活动状态。 Jeremy Manson 过去曾发过帖。我还记得看到来自 Joshua Bloch 的消息,但现在找不到它们。
多界面场景很有趣。当您有两个冲突的默认方法时会发生什么?
恭喜你提出了默认接口方法,作为一种更优雅的方式来完成 C# 使用其构思不当且实现相当丑陋的扩展方法所做的事情。至于这个问题,关于无法解决名称冲突的答案可以解决问题,但提供的其余原因是无法令人信服的语言学。 (如果我想要一个接口方法是最终的,那么你应该假设我必须有我自己非常好的理由想要禁止任何人提供与我不同的任何实现。)
@Trying 抽象类仍然是引入状态或实现核心 Object 方法的唯一方法。默认方法用于纯行为;抽象类用于与状态耦合的行为。
E
Edwin Dalorzo

在 lambda 邮件列表 there are plenty of discussions about it 中。其中一个似乎包含有关所有这些内容的大量讨论的内容如下:在 Varied interface method visibility (was Final defenders) 上。

在本次讨论中,original question 的作者 Talden 提出了与您的问题非常相似的问题:

将所有界面成员公开的决定确实是一个不幸的决定。在内部设计中对接口的任何使用都会暴露实现的私有细节,这是一个很大的问题。在不给语言添加一些晦涩难懂或破坏兼容性的细微差别的情况下,这是一个很难解决的问题。这种程度的兼容性中断和潜在的微妙之处将被视为不合情理,因此必须存在一个不会破坏现有代码的解决方案。重新引入“包”关键字作为访问说明符是否可行。接口中没有说明符意味着公共访问,而类中没有说明符意味着包访问。哪些说明符在接口中有意义尚不清楚 - 特别是如果为了最大限度地减少开发人员的知识负担,我们必须确保访问说明符在类和接口中的含义相同(如果它们存在)。在没有默认方法的情况下,我推测接口中成员的说明符必须至少与接口本身一样可见(因此该接口实际上可以在所有可见上下文中实现) - 默认方法不是如此确定。关于这是否是可能的范围内讨论,是否有任何明确的沟通?如果没有,是否应该在其他地方举行。

最终 Brian Goetz's answer 是:

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

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

在关于该主题的另一场激烈讨论about final defender methods中,Brian said again

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

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


我知道我应该在今天早上的谷歌搜索中包含旧名称“防御者方法”。 +1 挖掘这个。
很好地挖掘历史事实。您的结论与the official answer非常吻合
我不明白为什么它会破坏向后兼容性。 final 之前是不允许的。他们现在允许私有,但不受保护。 myeh ...私有无法实现...扩展另一个接口的接口可能实现父接口的一部分,但它必须向其他人公开重载能力...
M
Marco13

对于@EJP 的评论中提到的原因,很难找到和确定“THE”的答案:世界上大约有 2 (+/- 2) 人可以给出明确的答案 /em>。毫无疑问,答案可能只是“支持最终默认方法似乎不值得为重构内部调用解决机制而付出努力”。这当然是推测,但至少有微妙的证据支持,例如 Statement (by one of the two persons) in the OpenJDK mailing list

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

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

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


感谢您提供有趣的见解。约翰·罗斯的作品是一个有趣的踪迹。不过,我仍然不同意@EJP。作为反例,请查看我对 very interesting, very similar-style question by Peter Lawrey 的回答。 可以挖掘历史事实的,我总是很高兴在 Stack Overflow 上找到它们(还有其他地方吗?)。当然,你的回答仍然是推测性的,我不是 100% 相信 JVM 实现细节将是 JLS 以一种或另一种方式写下来的最终原因(双关语)......
@LukasEder 当然,这类问题很有趣,恕我直言,适合问答模式。我认为这里引起争议的关键有两点:第一,你问的是“原因”。在许多情况下,这可能只是没有正式记录在案。例如,JLS 中没有提到为什么没有无符号 int 的“原因”,但请参阅 stackoverflow.com/questions/430346 ... ...
... ... 第二个是您只要求“权威引用”,这将敢于写答案的人数从“几十”减少到......“大约为零”。除此之外,我不确定 JVM 的开发和 JLS 的编写是如何交织在一起的,即开发对 JLS 中写入的内容有多大影响,但是......我将避免在这里进行任何猜测; -)
我还在继续我的案子。看看谁回答了我的 other question :-) 现在有了权威的答案就永远清楚了,在这里 Stack Overflow 为什么决定不支持 default 方法上的 synchronized
@LukasEder 我明白了,这里也一样。谁能预料到呢?原因相当有说服力,特别是对于这个 final 问题,而且似乎没有其他人想到类似的例子(或者,也许有人考虑过这些例子,但觉得没有足够的权威来回答),这有点令人羞愧。所以现在(对不起,我必须这样做:)final 单词被说出。
s
skiwi

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

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

任何人都可以编写一个遵守界面的 Collection,然后以绝对违反直觉的方法执行操作,除了编写大量的单元测试之外,没有其他方法可以保护自己。


Javadoc 合同是我在问题中列出的 concrete 示例的有效解决方法,但问题实际上与便利接口方法用例无关。问题是关于为什么决定在 Java 8 interface 方法上不允许使用 final 的权威原因。成本/收益比不足是一个不错的选择,但到目前为止,这只是猜测。
A
Ashutosh

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

添加一个默认的最终方法。添加静态方法。

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