为什么在Java 8接口方法中不允许“final”?
Java 8的最有用的function之一是接口上的新的default
方法。 基本上有两个原因(可能还有其他原因)为什么被引入:
- 提供实际的默认实现。 示例:
Iterator.remove()
- 允许JDK API进化。 例如:
Iterable.forEach()
从APIdevise者的angular度来看,我希望能够在接口方法上使用其他修饰符,例如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
是一个class级,以上是已经很普遍的做法:
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
显然是矛盾的关键字,但默认关键字本身不会被严格要求 ,所以我假设这个矛盾是故意的,以反映“阶级方法与身体” (只是方法)之间的细微差异和“接口方法与身体” (默认方法),即差异,我还没有明白。
在某些时候,接口方法对static
和final
修饰符的支持还没有完全探索, 引用Brian Goetz :
另一部分是我们将在多大程度上支持接口中的类构build工具,如最终方法,私有方法,受保护方法,静态方法等。答案是:我们还不知道
从2011年底的那个时候起,很明显,在接口中增加了对static
方法的支持。 显然,这为JDK库自身增加了很多价值,比如Comparator.comparing()
。
题:
final
(也是static final
)从来没有将它作为Java 8接口的原因是什么?
这个问题在某种程度上与Java 8接口方法中不允许“同步”的原因有什么关系?
了解默认方法的关键是,主要的devise目标是界面的演变 ,而不是“把界面变成(平庸)的特质”。 虽然这两者之间有一些重叠,我们试图容纳后者,而不妨碍前者,从这个angular度来看,这些问题是最好的理解。 (还要注意,由于接口方法可以被多次inheritance,所以类方法将与接口方法有所不同,不pipe是什么意图。)
默认方法的基本思想是:它是一个默认实现的接口方法,派生类可以提供更具体的实现。 而且由于devise中心是接口的演进,因此devise中的一个关键devise目标就是能够以源兼容和二进制兼容的方式将默认方法添加到接口中。
对于“为什么不是最终的默认方法”的太简单的回答是,那么身体不会简单地作为默认的实现,它将是唯一的实现。 虽然这个问题有点太简单了,但是它给了我们一个线索,即问题已经在一个可疑的方向上了。
最终界面方法可能存在问题的另一个原因是它们给实现者带来了不可能的问题。 例如,假设你有:
interface A { default void foo() { ... } } interface B { } class C implements A, B { }
这里一切都很好, C从Ainheritancefoo()。现在假设B被更改为有一个foo方法,默认值为:
interface B { default void foo() { ... } }
现在,当我们去重新编译C时,编译器会告诉我们它不知道foo()
要inheritance什么行为,所以C必须覆盖它(并且可以select委托给A.super.foo()
if它想要保持相同的行为。)但是,如果B已经做出默认的最终决定,而A不在C的作者的控制之下呢? 现在C不可挽回地被打破; 如果不重写foo()
,它就不能编译,但如果它在B
是最终的,则它不能覆盖foo()
。
这只是一个例子,但重要的是,最终的方法实际上是一个工具,在单一inheritance类(通常将状态耦合到行为)的世界中变得更有意义,而不是仅仅贡献行为并且可以被多重inheritance的接口。 这很难说“哪些其他接口可能混入最终的实现者”,并且允许接口方法是最终的可能会导致这些问题(并且他们不会炸毁写这个接口的人,而是在试图实现它的穷人。)
阻止他们的另一个原因是他们不代表你的想法。 仅当类(或其超类)不提供方法的声明(具体或抽象)时才考虑默认实现。 如果默认方法是final的,但超类已经实现了这个方法,那么默认方法将被忽略,这可能不是默认作者在声明最终的时候期望的。 (这种inheritance行为是默认方法devise中心的一个反映 – 接口的演变,应该可以向现有的已经有实现的接口添加一个默认的方法(或者默认的实现),而不需要改变实现接口的现有类的行为,保证已经在缺省方法之前工作的类在缺省方法的情况下以相同的方式工作。
在lambda邮件列表中有很多关于它的讨论 。 其中一个似乎包含了所有的东西很多的讨论如下:在变化的接口方法可见性(是最后的捍卫者) 。
在这个讨论中, 原来问题的作者Talden问了一个和你的问题非常相似的东西:
让所有接口成员公开的决定确实是一个不幸的决定。 任何在内部devise中使用接口都暴露出实现私有细节是一个很大的问题。
这是一个艰难的修复,而不添加一些晦涩难懂或兼容性破坏语言的细微差别。 这种大小和潜在的微妙之间的兼容性突破将是不合情理的,因此必须存在不破坏现有代码的解决scheme。
可以重新引入“包”关键字作为访问说明符是可行的。 在接口中缺less一个说明符将意味着公共访问,并且类中没有指定符意味着包访问。 哪些说明符在接口中是有意义的,特别是如果为了最大限度地减less开发人员的知识负担,我们必须确保访问说明符在类和接口中是相同的,如果它们在场的话。
在没有默认方法的情况下,我推测接口中成员的说明符必须至less和接口本身一样可见(所以接口实际上可以在所有可见的上下文中实现) – 默认方法不是如此确定。
有没有明确的沟通,这是否是一个可能的范围内讨论? 如果不是,应该在其他地方举行。
最后Brian Goetz的回答是:
是的,这已经在探索中了。
但是,让我设定一些现实的期望 – 语言/虚拟机function有一个很长的时间,甚至像这样的琐碎看似。 为Java SE 8提出新的语言function创意的时间几乎已经过去了。
所以,很可能它从来没有实施,因为它从来不是范围的一部分。 这是从来没有提出的时间来考虑。
在关于最终辩护人关于这个问题的另一个热烈的讨论中, Brian又说 :
你已经得到了你想要的东西。 这正是这个function所增加的 – 行为的多重inheritance。 我们当然明白,人们会用它们作为特征。 我们努力确保他们所提供的inheritance模式简单,清洁,使得人们可以在各种各样的情况下获得好的结果。 同时,我们select不要把它们推到一个简单而干净的作品的边界之外,在某些情况下导致“你没有走得太远”的反应。 但是,真的,这个线程的大部分似乎是抱怨,玻璃只是98%充满。 我会拿这98%,继续下去!
所以这强化了我的理论,即它不是它们devise的范围或部分的一部分。 他们所做的就是提供足够的function来处理API演进的问题。
对于@EJP评论中提到的那些答案,很难find和确定“答案”:世界上大约有2(+/- 2)个人可以给出明确的答案。 毫无疑问,答案可能就像是“支持最终的默认方法似乎不值得重组内部呼叫解决机制”。 这当然是猜测,但它至less由微妙的证据支持,就像OpenJDK邮件列表中的这个声明(由两个人之一) :
“我想如果允许”最终默认“方法,他们可能需要从内部invokespecial重写到用户可见invokeinterface。
以及像这样的一些小事实,当一个方法是一个default
方法的时候,这个方法根本不被认为是一个(真的)最终的方法,正如目前在OpenJDK的Method :: is_final_method方法中实现的那样。
进一步真正的“authorative”信息确实很难find,即使是过多的websearches和阅读提交日志。 我认为这可能与解决与调用接口指令和类方法调用的接口方法调用过程中潜在的歧义有关,对应于invokeinterface
指令:对于invokevirtual
指令,可能有一个简单的vtable查找,因为方法必须要么从超类inheritance,要么由类直接实现。 与此相反, invokeinterface
调用必须检查相应的调用站点,以确定该调用实际引用哪个接口(这在HotSpot Wiki的InterfaceCalls页面中有更详细的说明)。 然而, final
方法根本不会插入到vtable中,或者replacevtable中的现有条目(参见klassVtable.cpp。第333行 ),同样,默认方法正在replacevtable中的现有条目(参见klassVtable.cpp, 202行 )。 所以实际的原因(也就是答案)必须在(相当复杂的)方法调用parsing机制中隐藏得更深,但是这些引用可能会被认为是有帮助的,只有其他人才能得出实际的答案从那。
我不认为有必要在快速界面方法中指定final
,但我可以同意,虽然这可能是有帮助的,但看起来成本已经超出了好处。
无论如何,你应该做的是为默认方法编写正确的javadoc,准确显示该方法是什么,不允许做什么。 这样,实现接口的类“不允许”改变实现,虽然没有保证。
任何人都可以写一个遵循界面的Collection
,然后用绝对不直观的方法来做事情,除了编写大量的unit testing外,没有办法保护自己。