默认方法是我们 Java 工具箱中一个不错的新工具。但是,我尝试编写一个接口(interface)来定义 toString
方法的 default
版本。 Java 告诉我这是被禁止的,因为在 java.lang.Object
中声明的方法可能不是 default
ed。为什么会这样?
我知道有“基类总是获胜”的规则,所以默认情况下(双关语;),任何 object
方法的 default
实现都将被覆盖Object
中的方法。但是,我认为规范中的 Object
方法不应该有异常(exception)。特别是对于 toString
,有一个默认实现可能非常有用。
那么,Java 设计者决定不允许 default
方法覆盖 Object
方法的原因是什么?
最佳答案
这是另一个看起来“显然是个好主意”的语言设计问题,直到您开始挖掘并意识到它实际上是个坏主意。
This mail有很多关于这个主题(以及其他主题)。有几种设计力量汇聚在一起,将我们带到了当前的设计中:
- 保持继承模型简单的愿望;
- 事实上,一旦你回顾了明显的例子(例如,将
AbstractList
变成一个接口(interface)),你就会意识到继承 equals/hashCode/toString 与单一继承和状态以及接口(interface)密切相关是多重继承和无状态的; - 这可能会为一些令人惊讶的行为打开大门。
您已经触及“保持简单”的目标;继承和冲突解决规则被设计得非常简单(类胜过接口(interface),派生接口(interface)胜过超接口(interface),任何其他冲突都由实现类解决。)当然,这些规则可以调整为异常(exception),但是我想当你开始拉弦时你会发现,增量的复杂性并不像你想象的那么小。
当然,有一定程度的好处可以证明更复杂是合理的,但在这种情况下它不存在。我们这里讨论的方法是equals、hashCode和toString。这些方法本质上都是关于对象状态的,拥有状态的是类,而不是接口(interface),谁最有资格确定平等对那个类意味着什么(尤其是平等的契约非常强;参见有效Java 带来了一些令人惊讶的后果);界面编写者离得太远了。
AbstractList
例子很容易拉出来;如果我们可以摆脱 AbstractList
并将行为放入 List
接口(interface)中,那就太好了。但是一旦你超越了这个明显的例子,就找不到很多其他好的例子了。从根本上讲,AbstractList
是为单一继承而设计的。但是接口(interface)必须为多重继承而设计。
进一步,假设你正在编写这个类:
class Foo implements com.libraryA.Bar, com.libraryB.Moo {
// Implementation of Foo, that does NOT override equals
}
Foo
作者查看父类(super class)型,没有看到 equals 的实现,并得出结论,要获得引用相等,他需要做的就是从 Object
继承 equals。然后,下周,Bar 的库维护者“有帮助地”添加了一个默认的 equals
实现。哎呀!现在 Foo
的语义已被另一个维护域中的接口(interface)破坏,“有用地”为常用方法添加了默认值。
默认值应该是默认值。向没有的接口(interface)(层次结构中的任何位置)添加默认值不应影响具体实现类的语义。但是,如果默认值可以“覆盖” Object 方法,那将是不正确的。
因此,虽然它看起来像是一个无害的特性,但实际上却是相当有害的:它增加了很多复杂性,只需要很少的增量表现力,而且它让善意的、看似无害的更改太容易单独编译接口(interface)破坏实现类的预期语义。
关于Java8 : Why is it forbidden to define a default method for a method from java. lang.Object,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24016962/