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);
}
如果 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,所以我假设这种矛盾是故意的,以反映“带主体的类方法”之间的细微差别< /em>(只是方法)和“与主体的接口方法”(默认方法),即我尚未理解的差异。
在某个时间点,尚未完全探索对接口方法上的 static
和 final
等修饰符的支持,citing Brian Goetz:
另一部分是我们将在接口中支持类构建工具的程度,例如最终方法,私有方法,受保护方法,静态方法等。答案是:我们还不知道
自 2011 年底以来,显然增加了对接口中 static
方法的支持。显然,这为 JDK 库本身增加了很多价值,例如 Comparator.comparing()
。
问题:
final
(以及 static final
)从未进入 Java 8 接口的原因是什么?
final
防止方法被覆盖,并且看到您必须如何覆盖从接口继承的方法,我不明白为什么将其设为 final 是有意义的。除非它表明该方法在覆盖它一次之后是最终的..在那种情况下,也许有困难?如果我不理解这一点,请让我知道。看起来很有趣
final
将用于防止实现类覆盖接口方法的默认实现。
这个问题在某种程度上与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 {
}
在这里,一切都很好; C
从 A
继承 foo()
。现在假设 B
更改为具有 foo
方法,具有默认值:
interface B {
default void foo() { ... }
}
现在,当我们重新编译 C
时,编译器会告诉我们它不知道要为 foo()
继承什么行为,因此 C
必须覆盖它(并且可以选择委托给 A.super.foo()
,如果它希望保留相同的行为。)但是如果 B
已将其设为默认 final
,并且 A
不受 C
的作者控制,该怎么办?现在 C
已不可挽回地损坏了;如果不覆盖 foo()
,它就无法编译,但如果它在 B
中是最终的,它就不能覆盖 foo()
。
这只是一个例子,但关键是方法的最终确定性实际上是一种工具,它在单继承类(通常将状态与行为耦合)的世界中比仅贡献行为并且可以成倍增加的接口更有意义遗传。很难推断“最终实现者中可能会混入哪些其他接口”,并且允许接口方法成为最终方法可能会导致这些问题(而且它们不会在编写接口的人身上爆炸,而是在试图实现它的可怜的用户。)
不允许它们的另一个原因是它们不会像您认为它们的意思一样。仅当类(或其超类)不提供方法的声明(具体或抽象)时才考虑默认实现。如果默认方法是最终方法,但超类已经实现了该方法,则默认方法将被忽略,这可能不是默认作者在声明它为最终方法时所期望的。 (这种继承行为反映了默认方法的设计中心——接口进化。应该可以将默认方法(或现有接口方法的默认实现)添加到已有实现的现有接口,而无需更改实现接口的现有类的行为,保证在添加默认方法之前已经工作的类在默认方法存在时以相同的方式工作。)
在 lambda 邮件列表 there are plenty of discussions about it 中。其中一个似乎包含有关所有这些内容的大量讨论的内容如下:在 Varied interface method visibility (was Final defenders) 上。
在本次讨论中,original question 的作者 Talden 提出了与您的问题非常相似的问题:
将所有界面成员公开的决定确实是一个不幸的决定。在内部设计中对接口的任何使用都会暴露实现的私有细节,这是一个很大的问题。在不给语言添加一些晦涩难懂或破坏兼容性的细微差别的情况下,这是一个很难解决的问题。这种程度的兼容性中断和潜在的微妙之处将被视为不合情理,因此必须存在一个不会破坏现有代码的解决方案。重新引入“包”关键字作为访问说明符是否可行。接口中没有说明符意味着公共访问,而类中没有说明符意味着包访问。哪些说明符在接口中有意义尚不清楚 - 特别是如果为了最大限度地减少开发人员的知识负担,我们必须确保访问说明符在类和接口中的含义相同(如果它们存在)。在没有默认方法的情况下,我推测接口中成员的说明符必须至少与接口本身一样可见(因此该接口实际上可以在所有可见上下文中实现) - 默认方法不是如此确定。关于这是否是可能的范围内讨论,是否有任何明确的沟通?如果没有,是否应该在其他地方举行。
最终 Brian Goetz's answer 是:
是的,这已经在探索中了。然而,让我设定一些现实的期望——语言/虚拟机特性有很长的交付周期,即使是像这样看似微不足道的特性。为 Java SE 8 提出新的语言特性想法的时间已经过去了。
因此,很可能它从未实施过,因为它从来都不是范围的一部分。它从未被及时提出以供考虑。
在关于该主题的另一场激烈讨论about final defender methods中,Brian said again:
你已经得到了你想要的。这正是这个特性所增加的——行为的多重继承。当然,我们知道人们会将它们用作特征。我们一直在努力确保他们提供的继承模型足够简单和干净,以便人们在各种情况下都能获得良好的结果。与此同时,我们选择不将它们推到简单而干净的工作范围之外,这在某些情况下会导致“哦,你做得不够远”的反应。但实际上,这个帖子的大部分似乎都在抱怨玻璃杯只有 98% 满了。我会接受 98% 并继续努力!
因此,这强化了我的理论,即它根本不是他们设计的范围或一部分的一部分。他们所做的是提供足够的功能来处理 API 演进的问题。
对于@EJP 的评论中提到的原因,很难找到和确定“THE”的答案:世界上大约有 2 (+/- 2) 人可以给出明确的答案 /em>。毫无疑问,答案可能只是“支持最终默认方法似乎不值得为重构内部调用解决机制而付出努力”。这当然是推测,但至少有微妙的证据支持,例如 Statement (by one of the two persons) in the OpenJDK mailing list:
“我想如果允许“最终默认”方法,它们可能需要从内部调用特殊重写为用户可见的调用接口。”
像这样的琐碎事实,当它是 default
方法时,根本不认为是(真正的)最终方法,目前在 OpenJDK 的 Method::is_final_method 方法中实现。
即使通过过多的网络搜索和阅读提交日志,也确实很难找到更多真正“权威”的信息。我认为这可能与invokeinterface
指令的接口方法调用和类方法调用的解析过程中的潜在歧义有关,对应于invokevirtual
指令:对于invokevirtual
指令,可能有一个简单的< strong>vtable 查找,因为该方法必须要么继承自超类,要么由该类直接实现。与此相反,invokeinterface
调用必须检查相应的调用站点以找出该调用实际引用的哪个 接口(这在 HotSpot Wiki 的 InterfaceCalls 页面中有更详细的说明)。但是,final
方法要么根本不插入到 vtable 中,要么替换 vtable 中的现有条目(参见 klassVtable.cpp. Line 333),同样,默认方法正在替换 vtable 中的现有条目(请参阅 klassVtable.cpp, Line 202)。因此,实际原因(因此,答案)必须隐藏在(相当复杂的)方法调用解析机制的更深处,但也许这些引用仍然会被认为是有帮助的,只是对其他人有用设法从中得出实际答案。
int
的“原因”,但请参阅 stackoverflow.com/questions/430346 ... ...
default
方法上的 synchronized
。
final
问题,而且似乎没有其他人想到类似的例子(或者,也许有人考虑过这些例子,但觉得没有足够的权威来回答),这有点令人羞愧。所以现在(对不起,我必须这样做:)final 单词被说出。
我认为没有必要在方便的接口方法上指定 final
,尽管我同意它可能会有所帮助,但似乎成本超过了收益。
无论哪种方式,您应该做的是为默认方法编写适当的 javadoc,准确显示该方法是什么,不允许做什么。这样,实现接口的类“不允许”更改实现,尽管不能保证。
任何人都可以编写一个遵守界面的 Collection
,然后以绝对违反直觉的方法执行操作,除了编写大量的单元测试之外,没有其他方法可以保护自己。
interface
方法上不允许使用 final
的权威原因。成本/收益比不足是一个不错的选择,但到目前为止,这只是猜测。
当我们知道扩展 interface
的类可能override
我们的实现时,我们将 default
关键字添加到 interface
内的方法中。但是如果我们想添加一个我们不希望任何实现类覆盖的方法呢?好吧,我们有两个选择:
添加一个默认的最终方法。添加静态方法。
现在,Java 表示,如果我们有一个 class
实现两个或多个 interfaces
,使得它们有一个具有完全相同的方法名称和签名的 default
方法,即它们是重复的,那么我们需要提供该方法的实现在我们班。现在在 default
final
方法的情况下,我们无法提供实现并且我们被卡住了。这就是为什么在接口中不使用 final
关键字的原因。