Java 8最有用的功能之一是接口上的新default
方法。基本上有两个原因(可能还有其他原因)为什么会被引入:
Iterator.remove()
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>“使用body的类方法”(只是方法)和“with body”的接口方法(默认方法),即我尚未理解的差异。
在某些时候,对接口方法的static
和final
等修饰符的支持尚未完全探索,citing Brian Goetz:
另一部分是我们将在多大程度上支持班级建设 接口中的工具,例如final方法,私有方法,protected 方法,静态方法等。答案是:我们还不知道
自2011年末以来,显然增加了对接口中static
方法的支持。显然,这为JDK库本身增加了很多价值,例如使用Comparator.comparing()
。
final
(以及static final
)从未进入Java 8接口的原因是什么?
答案 0 :(得分:385)
这个问题在某种程度上与What is the reason why “synchronized” is not allowed in Java 8 interface methods?
有关理解默认方法的关键是,主要的设计目标是接口进化,而不是&#34;将接口转换为(平庸)特征&#34;。虽然两者之间存在一些重叠,但我们试图适应后者,但它并没有妨碍前者,从这个角度来看,这些问题最好被理解。 (另请注意,类方法 与接口方法不同,无论意图如何,因为接口方法可以多次继承。)
默认方法的基本思想是:它是具有默认实现的接口方法,派生类可以提供更具体的实现。并且因为设计中心是接口演化,所以默认方法能够以源兼容和二进制兼容的方式在事件之后添加到接口是一个关键的设计目标。
过于简单的答案&#34;为什么不是最终的默认方法&#34;那就是那时身体不会只是默认的实现,它将是唯一的实现。虽然这个答案有点过于简单,但它为我们提供了一个线索,即问题已经朝着可疑的方向发展。
最终接口方法有问题的另一个原因是它们为实现者创建了不可能的问题。例如,假设你有:
interface A {
default void foo() { ... }
}
interface B {
}
class C implements A, B {
}
在这里,一切都很好; C
从foo()
继承A
。现在假设B
更改为foo
方法,默认值为:
interface B {
default void foo() { ... }
}
现在,当我们重新编译C
时,编译器会告诉我们它不知道要继承foo()
的行为,因此C
必须覆盖它(并且如果它想要保留相同的行为,可以选择委托给A.super.foo()
。)但如果B
使其默认final
并且A
不在控制C
的作者?现在C
已经无可挽回地被打破了;它可以在不覆盖foo()
的情况下进行编译,但如果它在foo()
中是最终的,则无法覆盖B
。
这只是一个例子,但重点是方法的终结性实际上是一个在单继承类(通常将状态与行为耦合)的世界中更有意义的工具,而不是仅仅贡献行为和可以多次继承。很难理解&#34;其他接口可能会混入最终的实现者&#34;,并且允许接口方法最终可能会导致这些问题(而且它们不会爆炸编写界面的人,但是试图实现它的穷人用户。)
禁止他们的另一个原因是他们并不意味着你认为他们的意思。仅当类(或其超类)不提供方法的声明(具体或抽象)时,才考虑默认实现。如果默认方法是final,但是超类已经实现了该方法,则默认值将被忽略,这可能不是默认作者在声明最终时所期望的。 (这种继承行为反映了默认方法的设计中心 - 接口演化。应该可以将默认方法(或现有接口方法的默认实现)添加到已经具有实现的现有接口,而无需更改实现接口的现有类的行为,保证在添加默认方法之前已经工作的类将在存在默认方法时以相同的方式工作。)
答案 1 :(得分:41)
在lambda邮件列表there are plenty of discussions about it中。其中一个似乎包含了很多关于所有这些内容的讨论如下:在Varied interface method visibility (was Final defenders)上。
在这次讨论中,original question的作者Talden提出的问题非常类似于你的问题:
让所有界面成员公开的决定确实是一个 不幸的决定。在内部设计中使用任何接口 暴露实施私人细节是一个很大的问题。
如果不添加一些模糊或兼容性,这是一个很难修复的问题 打破语言的细微差别。兼容性突破 大小和潜在的微妙之处将是不合情理的 解决方案必须存在,不会破坏现有代码。
可以重新引入'package'关键字作为访问说明符 可行的。它缺少界面中的说明符意味着 公共访问和类中缺少说明符意味着 包访问。哪个说明符在界面中有意义尚不清楚 - 特别是如果为了最大限度地减少开发人员的知识负担,我们必须确保访问说明符在两者中的含义相同 类和接口,如果它们存在。
在没有默认方法的情况下我猜测了 接口中成员的说明符必须至少与...一样可见 接口本身(所以接口实际上可以实现 所有可见的上下文) - 默认方法不太确定。
有没有明确的沟通,这是否是一个 可能的范围内讨论?如果没有,是否应该在其他地方举行。
是的,这已经在探索中了。
然而,让我设定一些现实的期望 - 语言/ VM 功能有很长的准备时间,即使像这样看似琐碎的。 为Java SE 8提出新语言功能想法的时间已经到了 几乎过去了。
所以,很可能它从未实现过,因为它从未成为范围的一部分。它从未被提议及时考虑。
关于这个主题的另一个热烈讨论about final defender methods,Brian said again:
你已经得到了你想要的东西。那正是如此 此功能添加了 - 多重行为继承。我们当然 明白人们会将它们作为特征使用。我们努力工作 确保他们提供的继承模型简单明了 足够干净,人们可以在广泛的范围内取得好成绩 各种情况。与此同时,我们选择不推动 它们超越了干净利落的界限 在某些情况下,导致“aw,你没有走得太远”的反应。但 真的,这个线程的大部分似乎都抱怨玻璃杯 只有98%满了。我会接受98%并继续使用它!
因此,这加强了我的理论,即它根本不属于其设计的范围或部分。他们所做的是提供足够的功能来处理API演变的问题。
答案 2 :(得分:16)
对于@EJP评论中提到的重复,很难找到并确定“答案”答案:世界上大约有2(+/- 2)人可以给出明确答案在所有。毫无疑问,答案可能只是“支持最终的默认方法似乎不值得重组内部呼叫解决机制”。当然,这是猜测,但它至少得到了微妙的证据支持,例如Statement (by one of the two persons) in the OpenJDK mailing list:
“我想如果允许”最终默认“方法,他们可能需要从内部invokespecial重写为用户可见的invokeinterface。”
和一些琐碎的事实,如果Method::is_final_method当前在InterfaceCalls中实现的话,方法根本不被认为是一个(真正的)最终方法OpenJDK中的方法。
即使有过多的网络搜索和阅读提交日志,确实很难找到真正的“授权”信息。我认为这可能与使用default
指令和类方法调用解决接口方法调用时的潜在歧义有关,对应于invokeinterface
指令:对于invokevirtual
指令,可能存在简单的 vtable 查找,因为该方法必须从超类继承,或者由类直接实现。与此相反,invokevirtual
调用必须检查相应的呼叫站点,以找出此呼叫实际引用的接口(这在klassVtable.cpp. Line 333页面中有更详细的解释)的HotSpot Wiki)。但是,invokeinterface
方法根本不会插入 vtable ,或者替换 vtable 中的现有条目(请参阅klassVtable.cpp, Line 202),同样,默认方法正在替换 vtable 中的现有条目(请参阅{{3}})。因此,实际的原因(以及答案)必须深入隐藏在(相当复杂的)方法调用解析机制中,但是这些引用可能会被认为是有用的,不管是对其他人而言设法从中得出实际的答案。
答案 3 :(得分:4)
我不认为在方便界面方法上指定final
是必要的,我可以同意它可能有用,但看起来成本超重了好处。
无论哪种方式,你应该做的是为默认方法编写正确的javadoc,准确显示方法是什么,不允许这样做。通过这种方式,不允许实现接口的类#34;改变实施,虽然没有保证。
任何人都可以编写一个符合界面的Collection
,然后在方法中做一些绝对反直觉的事情,除了编写大量的单元测试之外,没有办法保护自己。
答案 4 :(得分:0)
当我们知道扩展default
的类可能会或可能不会interface
实现时,我们会在interface
内的方法中添加override
关键字。但是,如果我们要添加一个我们不希望任何实现类覆盖的方法怎么办?好吧,我们有两个选择:
default
final
方法。static
方法。现在,Java说如果我们有一个class
实现两个或多个interfaces
,使得它们有一个default
方法,且方法名称和签名完全相同,即它们是重复的,则我们需要在我们的课程中提供该方法的实现。现在,在使用default
final
方法的情况下,我们无法提供实现并且陷入困境。这就是为什么在界面中不使用final
关键字的原因。