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


335

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

现在,default并且final显然是相互矛盾的关键字,但是默认关键字本身并没有严格要求,因此我假设这种矛盾是有意为之,以反映“带有主体的类方法”(正义方法)和“接口之间的细微差别。带主体的方法”(默认方法),即我尚未理解的差异。

在一定的时间点,对于像修饰符的支持staticfinal接口方法上还没有充分探讨,援引布赖恩戈茨

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

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

题:

是什么原因final(也static final没有)没有使用Java 8接口?


10
很抱歉,我被湿透了,但标题中表达的问题要在SO条款内得到回答的唯一方法是通过Brian Goetz或JSR Expert组的报价来解决。我了解BG已要求进行公开讨论,但这恰恰与SO的条款相抵触,因为它“主要基于意见”。在我看来,责任在这里被规避了。激发讨论并提出基本原理是专家组的工作,也是更广泛的Java Community Process的工作。不是这样的。因此,我投票决定以“主要基于意见”的身份关闭。
罗恩侯爵

2
众所周知,final防止方法被覆盖,并看到您必须如何覆盖从接口继承的方法,我不明白为什么将其定型是有意义的。除非要表明该方法在重写一次之后才是最终方法,否则会存在困难吗?如果我不了解这项权利,请让我知道。似乎很有趣
Vince Emigh 2014年

22
@EJP:“如果我能做到的话,你也可以,并回答自己的问题,因此不需要问。” 这几乎适用于该论坛上的所有问题,对吗?我总是可以自己花5个小时来搜索某个主题,然后学习其他所有人必须学习的同样困难的方法。或者,我们还要等待几分钟,以便有人给出更好的答案,以后每个人(包括到目前为止的12个赞誉和8个明星)都可以从中受益,因为在Google上引用如此之多。所以,是的。这个问题可以完全适合SO的问答形式。
卢卡斯·埃德

5
@VinceEmigh“ ...了解如何必须重写从接口继承的方法... ” Java 8中并非如此。Java8 允许您在接口中实现方法,这意味着您无需在实现中实现它们类。在这里,final可以用于防止实现类覆盖接口方法的默认实现。
awksp 2014年

28
@EJP你永远不会知道:Brian Goetz可能会回复
亚述2014年

Answers:


419

这个问题在某种程度上与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必须重写它(并且A.super.foo()如果希望保留相同的行为,可以选择委托给它。)但是如果B已经违约了final,并且A不受作者的控制C吗?现在C已经无法挽回了。它不能不覆盖就进行编译foo(),但是foo()如果它是final in 不能覆盖B

这只是一个例子,但重点是方法的确定性确实是一种工具,在单继承类(通常将状态与行为耦合)的世界中,比仅对行为做出贡献且可以相乘的接口更有意义。遗传。很难说出“最终实现器中可能还混入了其他什么接口”,而允许接口方法成为最终方法可能会导致这些问题(它们不会对编写接口的人大发雷霆,而会炸毁该接口的人)。尝试实现该目标的可怜用户。)

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


88
真高兴看到您回答有关新语言功能的问题!在弄清楚我们应该如何使用新功能时,弄清楚设计的意图和细节非常有帮助。是其他参与设计的人为SO做出了贡献吗?还是您自己来做?我将在java-8标签下关注您的答案-我想知道是否还有其他人在做同样的事情,所以我也可以关注他们。
Shorn

10
@Shorn Stuart Marks在java-8标记中一直处于活动状态。杰里米·曼森(Jeremy Manson)过去曾发表过。我还记得看到过约书亚·布洛赫(Joshua Bloch)发出的消息,但现在找不到。
亚述2014年

1
祝贺您提出默认的接口方法,这是一种更优雅的方法,可以完成C#对其构思不佳且相当笨拙的扩展方法所做的工作。关于这个问题,关于不可能解决名称冲突的答案解决了这个问题,但是提供的其余原因是不令人信服的语言学。(如果我希望接口方法是最终的,那么您应该假定我必须有自己的理由,以禁止任何人提供与我的不同的实现。)
Mike Nakis

1
尽管如此,通过将正在实现的特定接口的名称添加到实现方法的声明中,可以以类似于C#中的方式来实现名称冲突解决。因此,我从所有这些中得出的结论是,默认接口方法在Java中不可能是最终的,因为这将需要对语法进行其他修改,而您对此并不满意。(也
许太锐利

18
@Trying抽象类仍然是引入状态或实现核心Object方法的唯一方法。默认方法用于纯行为 ; 抽象类用于行为和状态。
Brian Goetz 2015年

43

在lambda邮件列表中,对此进行了大量讨论。关于所有这些东西的讨论似乎很多,其中之一是:在各种接口方法上的可见性(是Final Defender)

在此讨论中,原始问题的作者塔尔登问的问题与您的问题非常相似:

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

在不增加一些晦涩或兼容性破坏语言细微差别的情况下,这是一个很难解决的问题。如此巨大的兼容性突破和潜在的微妙之处看来是不合情理的,因此必须存在一个不会破坏现有代码的解决方案。

可以重新引入'package'关键字作为访问说明符是可行的。在接口中缺少说明符将意味着公共访问,而在类中缺少说明符则意味着程序包访问。尚不清楚接口中哪个说明符有意义-尤其是如果要最大程度地减少开发人员的知识负担,我们必须确保访问说明符在类和接口中都具有相同的含义(如果存在)。

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

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

最终,布莱恩·格茨(Brian Goetz)的答案是:

是的,这已经在探索中。

但是,让我设定一些现实的期望-语言/ VM功能的交付时间很长,甚至像这样的琐碎事物也是如此。为Java SE 8提出新的语言功能思想的时间已经过去了。

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

关于最终防御者方法的另一个激烈讨论中,Brian再次说

而且您已经完全得到了您想要的。这正是此功能添加的内容–行为的多重继承。当然,我们知道人们会把它们当作特质。我们一直在努力确保他们提供的继承模型足够简单和干净,以使人们可以在各种情况下取​​得良好的效果。同时,我们选择不将它们推到简单干净的范围之外,这在某些情况下会导致“哇,你走得不够远”的反应。但是,实际上,大部分线程似乎在抱怨玻璃杯只有98%充满。我会拿那98%继续下去!

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


4
我看到我今天早上在谷歌搜索的《奥德赛》中应该包含旧名称“防御者方法”。+1以进行深入挖掘。
Marco13,2014年

1
很好地挖掘历史事实。你的结论,对齐以及官方答案
卢卡斯埃德尔

我不明白为什么它会破坏向后兼容性。决赛是不允许的。现在,它们允许私有但不受保护。myeh ... private无法实现...扩展另一个接口的接口可能会实现父接口的一部分,但是它必须向其他用户公开重载功能...
mmm

17

这将是很难发现和识别“THE”的答案,在从@EJP的评论中提到的resons:有大约2(+/- 2)人在世界上,谁可以给定答案可言。毫无疑问,答案可能只是“支持最终的默认方法似乎不值得重新构造内部调用解析机制”。当然,这是推测,但至少有一些微妙的证据支持,例如OpenJDK邮件列表中的声明(由两个人之一)

“我想如果允许使用“最终默认”方法,则可能需要将它们从内部invokespecial重写为用户可见的invokeinterface。

诸如方法之类的琐碎事实,当它是一种方法时,根本就不认为它是(真正)最终default方法,正如目前在OpenJDK 中的Method :: is_final_method方法中实现的那样。

确实,即使进行过多的网络搜索并通过读取提交日志,也很难找到更多真正的“权威”信息。我认为这可能与invokeinterface指令和接口方法调用的接口方法调用解析过程中的潜在歧义有关,对应于该invokevirtual指令:对于该invokevirtual指令,可能有一个简单的vtable查找,因为该方法必须继承从超类或直接由该类实现。与此相反的是,一个invokeinterface电话必须检查相应的调用点,找出哪个接口这个调用实际上指的是(这在更详细的解释InterfaceCalls的热点维基页面)。然而,final方法都既不会被插入到虚函数表在所有,或替换现有条目虚表(参见klassVtable.cpp 333线),并且类似地,默认方法在替换现有条目虚表(参见klassVtable.cpp,202线)。因此,实际原因(以及答案)必须更深地隐藏在(相当复杂的)方法调用解析机制中,但是也许这些参考将被认为是有帮助的,因为它仅对那些设法得出实际答案的人有用。从那开始。


感谢您的有趣见解。约翰·罗斯(John Rose)的作品令人着迷。不过,我仍然不同意@EJP。作为反例,请查看我对彼得·劳瑞(Peter Lawrey)的一个非常有趣,非常相似的问题的回答。这有可能挖掘出的历史事实,我总是很高兴在这里找到他们的堆栈溢出(还有什么地方?)。当然,您的答案仍然是推测性的,我不是100%相信JVM实现细节将是以一种或另一种方式记录JLS的最终原因(双关语)……
Lukas Eder

@LukasEder当然,这些问题有趣,恕我直言也适合问答模式。我认为在这里引起争议的有两个关键点:首先是您要求“原因”。在许多情况下,这可能只是没有正式记录。例如,有在JLS为什么没有签名没有提到任何“理由” ints以下,见stackoverflow.com/questions/430346 ......
Marco13

……第二个是您要求“权威引文”,这使敢于写答案的人数从“几十个”减少到“大约为零”。除此之外,我不确定JVM的开发和JLS的编写是如何交织在一起的,即不确定该开发在多大程度上影响了JLS的内容,但是...我将避免任何猜测; -)
Marco13,2014年

1
我仍然休息。看看谁的回答我的其他问题 :-)现在将永远是明确有权威的答案,在这里对堆栈溢出为什么决定不支持synchronizeddefault方法。
卢卡斯·埃德

3
@LukasEder我明白了,这里也是。谁能想到呢?原因是相当令人信服的,尤其是对于这个final问题,并且令人沮丧的是,似乎没有其他人想到相似的示例(或者,也许有人对这些示例有所思考,但没有足够的权威去回答)。因此,现在(抱歉,我必须这样做:)说了最后一句话。
Marco13,2014年

4

我认为不必final在便捷的接口方法上进行指定,尽管可以有所帮助,但我可以同意,但似乎成本超过了收益。

无论哪种方式,您都应该为默认方法编写适当的javadoc,以准确显示该方法允许和不允许执行的操作。这样,尽管没有保证,但实现接口的类“不允许”更改实现。

任何人都可以编写Collection遵循界面的a,然后以绝对与直观相反的方法进行操作,除了编写大量的单元测试之外,没有其他方法可以使自己免受其害。


2
Javadoc合同是我在问题中列出的具体示例的有效解决方法,但问题实际上与便捷接口方法用例无关。问题是关于为何final决定不允许在Java 8 interface方法上使用的权威原因。成本/收益比不足是一个很好的选择,但是到目前为止,这只是猜测。
卢卡斯·埃德 Lukas Eder)

0

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

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

现在,Java表示,如果我们class实现了两个或多个,interfaces这样它们就拥有一个default方法名称和签名完全相同的方法,即它们是重复的,那么我们需要在类中提供该方法的实现。现在以防万一default final方法的,我们无法提供实现,因此陷入了困境。这就是为什么final在界面中不使用关键字的原因。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.