Java8:为什么禁止从java.lang.Object中定义一个方法的默认方法

默认的方法在我们的Java工具箱中是一个很好的新工具。 但是,我试图编写一个接口来定义toString方法的default版本。 Java告诉我这是被禁止的,因为在java.lang.Object声明的方法可能不是default 。 这是为什么?

我知道有“基类总是赢”的规则,所以在默认情况下(双关语), Object方法的任何default实现将被来自Object的方法覆盖。 但是,我没有看到为什么在规范中不应该有Object方法的exception。 特别是对于toString ,有一个默认的实现可能是非常有用的。

那么,Javadevise者为什么决定不允许default方法从Object覆盖方法的原因是什么呢?

这是另外一种语言devise问题,在你开始挖掘之前,似乎“显然是一个好主意”,你意识到这实际上是一个坏主意。

这封邮件有很多关于这个主题(以及其他主题)。有几个devise力量汇聚在一起,将我们带入当前的devise:

  • 保持inheritance模型简单的愿望;
  • 事实上,一旦你看过这些明显的例子(例如,把AbstractList变成一个接口),你意识到inheritanceequals / hashCode / toString与单一的inheritance和状态是紧密联系在一起的,接口是多inheritance和无状态的。
  • 这可能会打开一些令人惊讶的行为的大门。

你已经触及了“保持简单”的目标。 inheritance和冲突解决规则被devise得非常简单(类通过接口,派生接口胜过超接口,任何其他的冲突都由实现类来解决)。当然,这些规则可以被调整为例外,但是我想你会发现,当你开始使用这个string时,增量的复杂性并不像你想象的那么小。

当然,有一定程度的好处可以certificate更复杂,但在这种情况下,它并不存在。 我们在这里讨论的方法是equals,hashCode和toString。 这些方法本质上都是关于对象状态的,它是拥有状态的类,而不是接口,它最有能力确定这个类的平等意味着什么(特别是因为平等的契约是相当强的;见Effective Java的一些令人惊讶的后果); 接口编写者被删除得太远了。

抽取AbstractList示例很容易, 如果我们可以摆脱AbstractList并将行为放入List接口,那将是可爱的。 但是,一旦你超越这个明显的例子,就没有其他好的例子可以find。 在根目录下, AbstractList是为单一inheritance而devise的。 但接口必须devise为多inheritance。

此外,想象你正在写这个类:

 class Foo implements com.libraryA.Bar, com.libraryB.Moo { // Implementation of Foo, that does NOT override equals } 

Foo编写器查看超types,没有看到equals的实现,并得出结论:为了得到引用的相等性,他所需要做的就是从Objectinheritanceequals。 然后,下周,Bar的图书馆维护者“有用地”添加一个默认的equals实现。 哎呀! 现在Foo的语义已经被另一个维护域中的界面“有用地”添加了一个通用方法的默认值。

默认值应该是默认值。 将缺省值添加到没有任何接口(层次结构中的任何位置)的接口不应该影响具体实现类的语义。 但是,如果默认值可以“覆盖”对象方法,那就不对了。

所以,尽pipe这看起来像是一个无害的function,但实际上却是相当有害的:它增加了很多复杂性,增加了一点performance力,而且对于单独编译的界面的善意,无害的改变,实现类的预期语义。

禁止在java.lang.Object方法的接口中定义默认方法,因为默认方法永远不会“可达”。

默认的接口方法可以在实现接口的类中被覆盖,并且方法的类实现具有比接口实现更高的优先级,即使方法是在超类中实现的。 由于所有的类都是从java.lang.Objectinheritance的,因此java.lang.Object中的方法优先于接口中的默认方法,而不是被调用。

Oracle的Brian Goetz在这个邮件列表文章中提供了关于devise决策的更多细节。

我没有看到Java语言作者的头脑,所以我们只能猜测。 但我看到很多理由,绝对同意这个问题。

引入默认方法的主要原因是能够向接口添加新方法,而不会破坏旧版本的向后兼容性。 缺省方法也可以用来提供“方便”的方法,而不必在每个实现类中定义它们。

这些都不适用于toString和Object的其他方法。 简而言之,默认方法被devise为在没有其他定义的情况下提供默认行为。 不提供将与其他现有实现“竞争”的实现。

“基地总是胜利”的规则也有其坚实的原因。 假定类定义了真正的实现,而接口定义了默认的实现,这些实现稍微弱一些。

而且,对一般规则引入任何例外都会导致不必要的复杂性,并提出其他问题。 对象(或多或less)是一个类,为什么它有不同的行为?

总而言之,你提出的解决scheme可能会带来比专业人士更多的缺点。

推理非常简单,这是因为Object是所有Java类的基类。 所以即使在某个接口中将Object的方法定义为默认方法,也是无用的,因为总是使用Object的方法。 这就是为什么要避免混淆,我们不能有覆盖Object类方法的默认方法。

为了给出一个非常迂腐的答案,仅禁止从java.lang.Object公共方法定义default方法。 有11种方法可以考虑,可以用三种方法来回答这个问题。

  1. 6个Object方法不能有default方法,因为它们是final ,根本不能被覆盖: getClass()notify()notifyAll()wait()wait(long)wait(long, int)
  2. 由于Brian Goetz上面给出的原因, equals(Object)hashCode()toString() ,三个Object方法不能有default方法。
  3. 两个Object方法可以default方法,尽pipe这样的默认值是值得怀疑的: clone()finalize()

     public class Main { public static void main(String... args) { new FOO().clone(); new FOO().finalize(); } interface ClonerFinalizer { default Object clone() {System.out.println("default clone"); return this;} default void finalize() {System.out.println("default finalize");} } static class FOO implements ClonerFinalizer { @Override public Object clone() { return ClonerFinalizer.super.clone(); } @Override public void finalize() { ClonerFinalizer.super.finalize(); } } }