繁体   English   中英

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

[英]Java8: Why is it forbidden to define a default method for a method from java.lang.Object

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

我知道,有“基类总是胜”的规则,那么在默认情况下(双关语),任何default的实施Object方法将被重写从方法Object呢。 但是,我认为没有理由为什么规范中的Object方法不应该有例外。 特别是对于toString ,使用默认实现可能非常有用。

那么,Java设计者决定不允许default方法覆盖Object方法的原因是什么?

这是语言设计中的另一个问题,在您开始挖掘并且意识到这实际上是一个坏主意之前,这似乎是“显然是个好主意”。

这封邮件涉及很多主题(以及其他主题。)有多种设计力量融合在一起,使我们进入了当前的设计:

  • 保持继承模型简单的愿望;
  • 一旦查看了显而易见的示例(例如,将AbstractList转换为接口),您就会意识到,继承equals / hashCode / toString与单继承和状态紧密相关,并且接口是多重继承且无状态的;
  • 它可能为某些令人惊讶的行为打开了大门。

您已经达到了“保持简单”的目标。 继承和冲突解决规则的设计非常简单(类胜过接口,派生接口胜过超接口,其他任何冲突都由实现类解决。)当然可以对这些规则进行调整以使其成为异常,但是我想您会发现,当您开始使用该字符串时,增量复杂性并没有您想象的那么小。

当然,有一定程度的好处可以证明更多的复杂性是合理的,但是在这种情况下却不存在。 我们在这里讨论的方法是equals,hashCode和toString。 这些方法本质上都是关于对象状态的,并且拥有状态而不是接口的类是确定状态对该类意味着什么的最佳位置(尤其是当平等的契约非常牢固时;请参见有效)。 Java带来一些令人惊讶的后果); 接口编写器距离太远了。

提取AbstractList示例很容易; 如果我们可以摆脱AbstractList并将行为放入List接口,那将是很可爱的。 但是,一旦您超越了这个明显的示例,就找不到很多其他好的示例。 从根本上说, AbstractList是为单继承设计的。 但是接口必须设计用于多重继承。

此外,假设您正在编写此类:

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

Foo作者研究了超类型,没有看到equals的实现,并得出结论,要获得引用相等,他要做的就是从Object继承equals。 然后,下周,Bar的库维护者“有帮助地”添加了默认的equals实现。 哎呀! 现在, Foo的语义已被另一个“维护”域中的接口“有用地”破坏了,它为通用方法添加了默认值。

默认值应该是默认值。 向没有接口(层次结构中的任何地方)的接口添加默认值不应影响具体实现类的语义。 但是如果默认值可以“覆盖” Object方法,那将不是真的。

因此,尽管它看起来像是无害的功能,但实际上却是非常有害的:它增加了很多复杂性,几乎没有增量表达能力,而且对于原本意图良好,无害的更改进行单独编译的接口,破坏太容易了。实现类的预期语义。

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

可以在实现该接口的类中覆盖默认接口方法,并且即使该方法是在超类中实现的,该方法的类实现也比接口实现的优先级更高。 由于所有的类继承java.lang.Object ,在方法java.lang.Object必须优先于接口的默认方法来代替调用。

Oracle的Brian Goetz在此邮件列表中提供了有关设计决策的更多详细信息。

我看不到Java语言作者的头,所以我们只能猜测。 但是我看到许多原因,并且在这个问题上完全同意它们。

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

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

“基层总是赢”的规则也有其坚实的理由。 假设类定义了实际的实现,而接口定义了默认的实现,而后者则较弱。

另外,将ANY例外引入通用规则会导致不必要的复杂性并引发其他问题。 对象(或多或少)是一个类,为什么它应该具有不同的行为?

总而言之,您提出的解决方案可能会带来比专家更多的弊端。

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

为了给出一个非常古怪的答案,仅禁止从java.lang.Object公共方法定义default方法。 有11种方法可供考虑,可以通过三种方式进行分类来回答此问题。

  1. 其中六个Object方法不能具有default方法,因为它们是final方法,并且根本不能被覆盖: getClass()notify()notifyAll()wait()wait(long)wait(long, int)
  2. 由于上面的Brian Goetz给出的原因,三个Object方法不能具有default方法: equals(Object)hashCode()toString()
  3. Object方法中的两个可以具有default方法,尽管这样的默认值充其量是令人怀疑的: 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(); } } } 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM