Java 8最有用的功能之一是接口上的新 default
方法 . 基本上有两个原因(可能有其他原因)为什么会被引入:
-
提供实际的默认实现 . 示例:Iterator.remove()
-
允许JDK API演变 . 示例:Iterable.forEach()
从API设计者的角度来看,我希望能够在接口方法上使用其他修饰符,例如 final
. 这在添加便捷方法时非常有用,可以防止"accidental"覆盖实现类:
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
显然是矛盾的关键字,但默认关键字本身would not have been strictly required,所以我假设这个矛盾是故意的,以反映"class methods with body"(只是方法)和"interface methods with body"(默认方法)之间的细微差别,即我的差异还没有明白 .
在某些时候,对接口方法的修饰符如 static
和 final
的支持尚未完全探索,citing Brian Goetz:
另一部分是我们将在接口中支持类构建工具的程度,例如最终方法,私有方法,受保护方法,静态方法等 . 答案是:我们还不知道
从2011年末的那个时候开始,显然增加了对接口中 static
方法的支持 . 显然,这为JDK库本身增加了很多 Value ,例如Comparator.comparing() .
问题:
final
(以及 static final
)从未进入Java 8接口的原因是什么?
4 回答
这个问题在某种程度上与What is the reason why “synchronized” is not allowed in Java 8 interface methods?有关
理解默认方法的关键是主要设计目标是界面演变,而不是"turn interfaces into (mediocre) traits" . 虽然有可能妨碍前者,但从这个角度来看,这些问题最能被理解 . (另请注意,由于接口方法可以多次继承,因此无论意图如何,类方法都将与接口方法不同 . )
默认方法的基本思想是:它是具有默认实现的接口方法,派生类可以提供更具体的实现 . 而且由于设计中心是接口演化,因此默认方法能够以源兼容和二进制兼容的方式添加到接口之后是一个关键的设计目标 .
对“为什么不是最终默认方法”的过于简单的回答是,然后主体不仅仅是默认实现,它将是唯一的实现 . 虽然这个答案有点过于简单,但它为我们提供了一个线索,即问题已经朝着可疑的方向发展 .
最终接口方法可疑的另一个原因是它们为实现者创建了不可能的问题 . 例如,假设您有:
一切都很好;
C
从A
继承foo()
. 现在假设B
被更改为具有foo
方法,具有默认值:现在,当我们重新编译
C
时,编译器会告诉我们它不知道要为foo()
继承什么行为,所以C
必须覆盖它(并且如果它想要保留相同的行为,可以选择委托给A.super.foo()
. )但是如果B
已经使其默认final
,并且A
不受C
作者的控制,该怎么办?现在C
已经无可挽回地破裂了;如果没有覆盖foo()
,它就无法编译,但如果它在B
中是最终的,则无法覆盖foo()
.这只是一个例子,但重点是最终方法实际上是一个在单继承类(通常将状态与行为耦合)的世界中更有意义的工具,而不是仅仅贡献行为并且可以多次继承的接口 . 很难推断“其他接口可能会混入最终的实现者”,并且允许接口方法最终可能会导致这些问题(并且它们不会在编写接口的人身上爆炸,而是在试图实现它的穷人 . )
不允许他们的另一个原因是他们不会意味着你认为他们的意思 . 仅当类(或其超类)不提供方法的声明(具体或抽象)时,才考虑默认实现 . 如果默认方法是final,但是超类已经实现了该方法,则默认值将被忽略,这可能不是默认作者在声明最终时所期望的 . (这种继承行为反映了默认方法的设计中心 - 接口演化 . 应该可以将默认方法(或现有接口方法的默认实现)添加到已经具有实现的现有接口,而无需更改现有类的行为实现接口,保证在添加默认方法之前已经工作的类在默认方法存在时将以相同的方式工作 . )
在lambda邮件列表there are plenty of discussions about it中 . 其中一个似乎包含了很多关于所有这些东西的讨论如下:在Varied interface method visibility (was Final defenders)上 .
在这次讨论中,original question的作者Talden问你的问题非常相似:
最终Brian Goetz's answer是:
所以,很可能它从未实现过,因为它从未成为范围的一部分 . 它从未被提议及时考虑 .
在关于这个问题的另一个激烈讨论about final defender methods,Brian said again:
因此,这加强了我的理论,即它根本不属于其设计的范围或部分 . 他们所做的是提供足够的功能来处理API演变的问题 .
对于@EJP评论中提到的那些人来说,很难找到并确定答案:世界上大约有2(/ - 2)人可以给出明确的答案 . 而且毫无疑问,答案可能就像"Supporting final default methods did not seem to be worth the effort of restructuring the internal call resolution mechanisms" . 当然,这是猜测,但它至少得到了微妙的证据支持,比如Statement (by one of the two persons) in the OpenJDK mailing list:
和一些简单的事实一样,当一个方法是
default
方法时,它根本不被认为是一个(真正的)最终方法,正如当前在OpenJDK中的Method::is_final_method方法中实现的那样 .事实上,即使有过多的网络搜索和阅读提交日志,确实很难找到"authorative"信息 . 我认为它可能与使用
invokeinterface
指令和类方法调用解决接口方法调用时的潜在歧义有关,对应于invokevirtual
指令:对于invokevirtual
指令,可能有一个简单的 vtable 查找,因为该方法必须要么从超类继承,要么直接由类实现 . 与此相反,invokeinterface
调用必须检查相应的调用站点以找出此调用实际引用的接口(这在HotSpot Wiki的InterfaceCalls页面中有更详细的解释) . 但是,final
方法根本不会插入到 vtable 中,或者替换 vtable 中的现有条目(请参阅klassVtable.cpp. Line 333),同样,默认方法正在替换 vtable 中的现有条目(请参阅klassVtable.cpp, Line 202) . 因此,实际原因(以及答案)必须更深入地隐藏在(相当复杂的)方法调用解析机制中,但是这些引用可能会被认为是有用的,只有其他人才能设法推导出来 . 实际的答案 .我不认为在方便界面方法上指定
final
是必要的,我同意虽然它可能有帮助,但看起来成本超出了好处 .无论哪种方式,你应该做的是为默认方法编写正确的javadoc,准确显示方法是什么,不允许这样做 . 以这种方式,实现接口的类“不允许”改变实现,尽管没有保证 .
任何人都可以编写一个符合界面的
Collection
,然后在方法中做一些绝对反直觉的事情,除了编写大量的单元测试之外,没有办法保护自己 .