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


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

在某些时候,支持尚未完全探讨接口方法上的finalstatic之类的修饰符,引用Brian Goetz的话:


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



自2011年末以来,很明显,在接口中添加了对final方法的支持。显然,这为JDK库本身增加了很多价值,例如static

问题: Java 8接口?

评论

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

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

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

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

@EJP你永远不会知道:Brian Goetz可能会回复!

#1 楼

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

了解默认方法的关键是主要设计目标是界面演变,而不是“将界面转变为(中等)特征”。虽然两者之间存在一些重叠,并且我们试图适应后者所没有的障碍,但是从这种角度来看,最好理解这些问题。 (还要注意,由于接口方法可以被多重继承的事实,无论是什么意图,类方法都将与接口方法有所不同。)是:这是具有默认实现的接口方法,派生类可以提供更具体的实现。而且由于设计中心是接口的演进,因此一个关键的设计目标是能够在事后以兼容源和兼容二进制的方式将默认方法添加到接口中。

对“为什么不是最终的默认方法”的答案太简单了,那就是主体将不再仅仅是默认的实现,而是唯一的实现。尽管答案太简单了,但它为我们提供了一个线索,即问题已经朝着可疑的方向发展。

最终接口方法令人质疑的另一个原因是,它们对实现者造成了不可能的问题。例如,假设您有:

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

interface B { 
}

class C implements A, B { 
}


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

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


现在,当我们重新编译C时,编译器将告诉我们它不知道为foo()继承什么行为,因此C必须重写它(如果希望保留相同的行为,可以选择委托给A.super.foo()。 )但是,如果B已将其默认设置为final,并且A不受C的作者的控制,该怎么办?现在,C已损坏,无法挽回。它不能在不覆盖foo()的情况下进行编译,但是如果它在foo()中是最终的,则不能覆盖B

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

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

评论


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

– Shorn
2014年5月7日,下午1:46

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

–叙利亚
2014年5月7日9:05



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

– Mike Nakis
14年6月20日,0:15

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

– Mike Nakis
2014年6月20日下午0:16

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

–布赖恩·格茨(Brian Goetz)
15/12/27在20:05

#2 楼

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

在本次讨论中,原始问题的作者塔尔登问一个与您的问题非常相似的内容:


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

在不增加一些晦涩或兼容性的情况下进行修复很困难。 。这么大的兼容性破坏和潜在的微妙之处似乎是不合理的,因此必须存在一个不会破坏现有代码的解决方案。一个访问说明符是可行的。接口中没有指定符意味着
公共访问,而类中没有指定符意味着
包访问。在接口中哪个说明符有意义尚不清楚
-尤其是如果要最大程度地减少开发人员的知识负担,我们必须确保访问说明符在类和接口中的含义相同。存在。

在没有默认方法的情况下,我推测接口中成员的
说明符必须至少与接口本身(<因此该接口实际上可以在
所有可见的上下文中实现)-使用不确定的默认方法。可能进行范围内的讨论?如果不是这样,应该在其他地方举行。最终,布莱恩·格茨(Brian Goetz)的答案是:

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



因此,很可能从未实现,因为它从来没有范围。从来没有及时提出过建议。

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


正是您所希望的。这正是此功能添加的内容-行为的多重继承。当然,我们
理解人们会把它们当作特质。我们一直在努力
,以确保他们提供的继承模型简单且
足够干净,以使人们在各种各样的情况下都能获得良好的结果。同时,我们选择了不要将它们
推到简单,干净的工作范围之外,并且在某些情况下,导致
产生“ aw,你走得不够远”的反应。但是
实际上,大部分线程似乎都在抱怨玻璃杯是否只有98%充满。我将拿出98%并继续使用它!


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

评论


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

– Marco13
2014年5月4日17:00

很好地挖掘历史事实。您的结论与官方回答完全吻合

–卢卡斯·埃德(Lukas Eder)
2014年5月5日下午16:34

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

–mmm
20-3-9在16:27



#3 楼

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


允许使用“最终默认”方法,它们可能需要从内部invokespecial重写为用户可见的invokeinterface。“ )最终方法(当它是default方法时),如当前在OpenJDK中的Method :: is_final_method方法中实现的那样。

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

评论


感谢您的有趣见解。约翰·罗斯的作品很有意思。我仍然不同意@EJP。作为反例,请查看我对彼得·劳瑞(Peter Lawrey)一个非常有趣,非常相似的问题的回答。可以挖掘历史事实,而我总是很高兴在Stack Overflow上找到它们(还有其他地方?)。当然,您的答案仍然是推测性的,我不是100%相信JVM实现细节将是用一种或另一种方式记录JLS的最终原因(双关语)。

–卢卡斯·埃德(Lukas Eder)
2014年5月4日13:57

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

– Marco13
2014年5月4日14:15

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

– Marco13
2014年5月4日14:15

我仍然休息。看看谁回答了我的另一个问题:-)有了权威性的答案,这将永远很清楚,在Stack Overflow上,为什么决定不支持默认方法的同步。

–卢卡斯·埃德(Lukas Eder)
2014年5月5日7:51



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

– Marco13
2014年5月5日19:57

#4 楼

我认为不必在方便的接口方法上指定final,我可以同意,尽管它可能会有所帮助,但似乎成本超过了收益。不管哪种方式,都是为默认方法编写适当的javadoc,以准确显示该方法是什么以及不允许做的事情。这样,尽管没有保证,但实现接口的类“不允许”更改实现。绝对是反直观的,除了编写大量的单元测试之外,没有其他方法可以使自己免受其害。

评论


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

–卢卡斯·埃德(Lukas Eder)
2014年5月4日13:44

#5 楼

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


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