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

时间:2014-06-03 13:50:37

标签: java interface java-8 default-method

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

我知道"基类总是赢得"规则,所以默认情况下(pun;),default方法的任何Object实现都会被Object的方法覆盖。但是,我认为没有理由说明规范中Object的方法不应该是例外。特别是对于toString,拥有默认实现可能非常有用。

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

5 个答案:

答案 0 :(得分:170)

这是另一个语言设计问题,似乎是一个好主意"直到你开始挖掘,你才意识到它实际上是一个坏主意。

This mail在这个问题上有很多(以及其他主题。)有几种设计力量汇聚在一起,将我们带入当前的设计:

  • 保持继承模型简单的愿望;
  • 一旦你看过明显的例子(例如,将AbstractList转换为接口),你就会意识到继承equals / hashCode / toString与单继承和状态紧密相关,接口是多重继承的和无国籍;
  • 这可能为一些令人惊讶的行为打开了大门。

你已经触及了"保持简单"目标;继承和冲突解决规则设计得非常简单(类胜过接口,派生接口胜过超接口,任何其他冲突都由实现类解决。)当然这些规则可以调整为异常,但是我认为当你开始使用该字符串时,你会发现增量复杂性并不像你想象的那么小。

当然,有一定程度的好处可以证明更复杂,但在这种情况下它并不存在。我们在这里讨论的方法是equals,hashCode和toString。这些方法本质上都是关于对象状态的,并且它是拥有状态而不是接口的类,它最有能力确定该等级对于该类的意义(特别是对于相等的契约非常强;请参阅有效Java有一些令人惊讶的后果);接口编写器太远了。

很容易取出AbstractList示例;如果我们可以摆脱AbstractList并将行为放入List界面,那将是可爱的。但是,一旦你超越了这个明显的例子,就没有很多其他好的例子可以找到。在root用户,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的语义已经被另一个维护域中的接口打破了#34;有帮助的"添加常用方法的默认值。

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

所以,虽然它看起来像一个无害的功能,但它实际上是非常有害的:它增加了很多复杂性,几乎没有增量表达性,并且它使得单独编译的善意,无害的变化太容易了。用于破坏实现类的预期语义的接口。

答案 1 :(得分:28)

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

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

来自Oracle的Brian Goetz在此mailing list post中提供了有关设计决策的更多详细信息。

答案 2 :(得分:3)

我没有看到Java语言作者的头脑,所以我们可能只会猜测。但是我在这个问题上看到了很多理由并且完全赞同它们。

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

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

“基类永远胜利”规则也有其坚实的理由。假设类定义真正的实现,而接口定义默认实现,这些实现稍微弱一些。

此外,对一般规则引入任何例外会导致不必要的复杂性并引发其他问题。对象(或多或少)是一个类,所以为什么它应该有不同的行为?

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

为了给出一个非常迂腐的答案,我们只能禁止从default 公共 方法定义java.lang.Object方法。有11种方法需要考虑,可以通过三种方式对这个问题进行分类。

  1. 其中六个Object方法不能使用default方法,因为它们为final且根本无法覆盖:getClass()notify(),{{1 },notifyAll()wait()wait(long)
  2. 由于Brian Goetz上面给出的原因,wait(long, int)方法中的三种方法不能使用Object方法:defaultequals(Object)hashCode()
  3. 其中两个toString()方法可以拥有Object方法,但这些默认值的值最多是值得怀疑的:default和{{ 1}}。

    clone()