为什么Java 8接口方法中不允许"final"?

Luk*_*der 324 java language-design java-8 jsr335 default-method

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);
}
Run Code Online (Sandbox Code Playgroud)

如果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);
}
Run Code Online (Sandbox Code Playgroud)

现在,defaultfinal有明显矛盾的关键字,但默认关键字本身不会一直严格要求,所以我假设这个矛盾是经过深思熟虑的,反映之间的细微差别"类方法与身体"(只是方法)和"接口有身体的方法"(默认方法),即我尚未理解的差异.

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

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

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

题:

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

Bri*_*etz 402

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

理解默认方法的关键是主要设计目标是界面演化,而不是"将界面转换为(平庸)特征".虽然两者之间存在一些重叠,但我们试图适应后者,但它并没有妨碍前者,从这个角度来看,这些问题最好被理解.(请注意,由于接口方法可以多次继承,因此无论意图如何,类方法将与接口方法不同.)

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

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

最终接口方法可疑的另一个原因是它们为实现者创建了不可能的问题.例如,假设你有:

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

interface B { 
}

class C implements A, B { 
}
Run Code Online (Sandbox Code Playgroud)

一切都很好; C继承foo()A.现在假设B已更改为具有foo默认值的方法:

interface B { 
    default void foo() { ... }
}
Run Code Online (Sandbox Code Playgroud)

现在,当我们去重新编译时C,编译器会告诉我们它不知道要继承什么行为foo(),所以C必须覆盖它(A.super.foo()如果它想要保留相同的行为,可以选择委托.)但是如果B已经默认final,并且A不受作者的控制C?现在C已经无可挽回地破裂了; 如果没有覆盖它就无法编译foo(),但foo()如果它是最终的,则无法覆盖B.

这只是一个例子,但关键是最终方法实际上是一个在单继承类(通常将状态与行为耦合)的世界中更有意义的工具,而不是仅仅贡献行为并且可以多次继承的接口.很难推断出"其他接口可能会混入最终的实现者",并且允许接口方法最终可能会导致这些问题(并且它们不会在编写接口的人身上爆炸,而是在试图实现它的穷人.)

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

  • 很高兴看到你回答有关新语言功能的问题!在确定*我们应该如何使用新功能时,澄清设计的意图和细节非常有帮助.参与设计的其他人是否也为SO做出了贡献 - 或者您自己就是这样做的?我将在java-8标签下关注你的答案 - 我想知道是否还有其他人这样做,所以我也可以关注它们. (87认同)
  • @Trying Abstract类仍然是引入状态或实现核心Object方法的唯一方法.默认方法是*纯行为*; 抽象类用于与状态相结合的行为. (15认同)
  • @Shorn [Stuart Marks](http://stackoverflow.com/users/1441122/stuart-marks)已经在java-8标签中处于活动状态.[Jeremy Manson](http://stackoverflow.com/users/2189686/jeremy-manson)已发布过去.我还记得看到来自Joshua Bloch的消息但现在找不到它们. (10认同)
  • 恭喜您提出了默认接口方法,这是一种更优雅的方式来完成 C# 以其构思不当且实现起来相当丑陋的扩展方法所做的事情。对于这个问题,关于无法解决名称冲突的答案某种程度上解决了问题,但提供的其余原因在语言学上并不令人信服。(如果我希望接口方法是最终的,那么您应该假设我必须有自己非常充分的理由来禁止任何人提供与我不同的实现。) (3认同)

Edw*_*rzo 42

在lambda邮件列表中有很多关于它的讨论.其中一个似乎包含了很多关于所有内容的讨论如下:On Varied接口方法可见性(最终防御者).

在这个讨论中,Talden,原始问题的作者提出了一些与你的问题非常相似的东西:

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

如果没有在语言中添加一些模糊或兼容性的细微差别,这是一个很难修复的问题.这种程度和潜在微妙的兼容性突破将被视为不合情理,因此必须存在不破坏现有代码的解决方案.

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

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

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

最终Brian Goetz的回答是:

是的,这已经在探索中了.

但是,让我设定一些现实的期望 - 语言/虚拟机功能需要很长的准备时间,即使是像这样看似微不足道的.为Java SE 8提出新语言功能想法的时间已经过去了.

因此,很可能它从未实现过,因为它从未成为范围的一部分.它从未被提议及时考虑.

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

你已经得到了你想要的东西.这正是此功能添加的内容 - 多重行为继承.当然,我们理解人们会将它们作为特征使用.我们努力确保他们提供的继承模式简单而干净,以便人们可以在各种各样的情况下取得良好的效果.与此同时,我们选择不将它们推到简单干净的范围之外,并且在某些情况下会导致"哇,你没有走得太远"的反应.但实际上,这个主题的大部分内容似乎都抱怨玻璃只有98%满了.我会拿98%继续吧!

因此,这加强了我的理论,即它不仅仅是其设计的范围或部分.他们所做的是提供足够的功能来处理API演变的问题.

  • 我知道我应该在今天早上的谷歌搜索奥德赛中加入旧名称"防御者方法".+1用于挖掘它. (4认同)

Mar*_*o13 16

这将是很难发现和识别"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).因此,实际原因(以及答案)必须更深入地隐藏在(相当复杂的)方法调用解析机制中,但是这些引用可能仍然被认为是有用的,只有其他人才能获得实际的答案.从那以后.

  • @LukasEder我看,这里也一样.谁能想到呢?原因相当令人信服,特别是对于这个"最终"的问题,并且有些谦卑,没有其他人似乎想到类似的例子(或者,也许,有些人想到这些例子,但是没有足够的认真回答).所以现在(对不起,我必须这样做:)最终*单词被说出来. (3认同)

归档时间:

查看次数:

31512 次

最近记录:

6 年,5 月 前