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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么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显然是矛盾的关键字,但默认关键字本身是would not have been strictly required,所以我假设这个矛盾是故意的,以反映“类方法与身体”(只是方法)和“与身体的接口方法”之间的细微差别“(默认方法),即我尚未理解的差异。

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

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

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

Question:

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

答案

这个问题在某种程度上与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  

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

interface B  
    default void foo()  ... 

现在,当我们重新编译C时,编译器将告诉我们它不知道继承foo()的行为,因此C必须覆盖它(如果它想要保留相同的行为,可以选择委托给A.super.foo()。 )但是如果B制作了它的默认finalA不受C作者的控制怎么办?现在C无可挽回地被打破了;如果没有覆盖foo(),它就无法编译,但如果它在foo()中是最终的,则无法覆盖B

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

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

另一答案

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

在这次讨论中,original question的作者Talden提出的问题非常类似于你的问题:

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

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

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

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

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

最终Brian Goetz's answer是:

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

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

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

在另一个激烈的讨论about final defender methods关于这个问题,Brian said again

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

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

另一答案

对于@EJP评论中提到的重复,很难找到并确定“答案”的答案:世界上大约有2(+/- 2)人可以给出明确的答案。毫无疑问,答案可能只是“支持最终的默认方法似乎不值得重组内部呼叫解决机制”。当然,这是猜测,但它至少得到了微妙的证据支持,比如这个Statement (by one of the two persons) in the OpenJDK mailing list

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

这是一个简单的事实,当它是一个default方法时,根本不被认为是一个(真正的)最终方法,正如目前在OpenJDK中的Method::is_final_method方法中实现的那样。

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

另一答案

我不认为有必要在便利界面方法上指定final,我可以同意它虽然可能有帮助,但似乎成本超出了好处。

无论哪种方式,你应该做的是为默认方法编写正确的javadoc,准确显示方法是什么,不允许这样做。以这种方式,实现接口的类“不允许”改变实现,尽管没有保证。

任何人都可以编写一个符合界面的Collection,然后在方法中做一些绝对反直觉的事情,除了编写大量的单元测试之外,没有办法保护自己。

以上是关于为什么Java 8接口方法中不允许“final”?的主要内容,如果未能解决你的问题,请参考以下文章

Java 8 接口方法中不允许“同步”的原因是啥?

为啥接口方法不能是“静态的”和“最终的”?

为什么接口方法不能“静态”和“最终”?

什么叫final修饰符?有什么作用?

Java 8 函数式接口 - Functional Interface

8 面向对象之抽象类+接口+内部类