这是另一个语言设计问题,似乎“显然是一个好主意”,直到你开始挖掘,你意识到它实际上是一个坏主意。
这封邮件有很多关于这个主题(以及其他主题)。有几个设计力量汇聚在一起,将我们带到了当前的设计:
- 希望保持继承模型简单;
- 事实上,一旦你忽略了明显的例子(例如,变成一个接口),你就会意识到继承equals/hashCode/toString与单继承和状态密切相关,接口是多重继承和无状态的;
AbstractList
- 它可能会为一些令人惊讶的行为打开大门。
您已经谈到了“保持简单”的目标;继承和冲突解决规则被设计得非常简单(类胜过接口,派生接口胜过超接口,任何其他冲突都由实现类解决。当然,这些规则可以调整为例外,但我认为当你开始拉动这个字符串时,你会发现增量的复杂性并不像你想象的那么小。
当然,有一定程度的好处可以证明更复杂的合理性,但在这种情况下,它不存在。我们在这里谈论的方法是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
}
作者查看了超类型,没有看到 equals 的实现,并得出结论,要获得引用相等,他需要做的就是从 继承 equals。然后,下周,Bar的库维护者“有帮助地”添加了一个默认实现。哎呀!现在,语义已被另一个维护域中的接口“有益地”破坏,为通用方法添加了默认值。Foo
Object
equals
Foo
默认值应该是默认值。将默认值添加到没有默认值的接口(层次结构中的任何位置)不应影响具体实现类的语义。但是,如果默认值可以“重写”Object 方法,那就不是这样了。
因此,虽然它似乎是一个无害的功能,但实际上它非常有害:它增加了很多复杂性,几乎没有增量表达能力,并且它使对单独编译的接口进行善意,无害的更改太容易破坏实现类的预期语义。