为什么Java的Area#equals方法没有覆盖Object#equals方法?

23

我遇到了一个由Java的java.awt.geom.Area#equals(Area)方法引起的问题。这个问题可以简化为以下单元测试:

@org.junit.Test
public void testEquals() {
    java.awt.geom.Area a = new java.awt.geom.Area();
    java.awt.geom.Area b = new java.awt.geom.Area();
    assertTrue(a.equals(b)); // -> true

    java.lang.Object o = b;
    assertTrue(a.equals(o)); // -> false
}

我经过一番思考和调试,最终在JDK源代码中看到了Area类的equals方法的签名如下:

public boolean equals(Area other)
请注意,它不会覆盖来自Object的普通equals方法,而只是用更具体的类型重载该方法。因此,上面示例中的两个调用最终会调用不同的equals实现。
由于这种行为自Java 1.2以来就存在,因此我认为它不被认为是一个错误。因此,我更感兴趣的是找出为什么决定不正确地覆盖equals方法,同时提供重载版本。(另一个暗示这是一个实际决策的迹象是覆盖hashCode()方法的缺失。)
我唯一的猜测是作者担心用于比较面积相等的缓慢的equals实现不适合将Area放入Set、Map等数据结构中进行比较。 (在上面的示例中,你可以将a添加到HashSet中,虽然b等于a,但调用contains(b)将失败。) 然后,他们为什么不只是以不与equals方法这样一个基本概念冲突的方式命名可疑的方法呢?

1
请注意,hashCode 也没有被重写。我认为他们在这里做的只是提供一个“比较”方法,恰好与 Object.equals() 同名。个人而言,我会给它取一个不同的名称以避免混淆。 - RealSkeptic
Java API并不总是遵循最佳实践,这似乎是一个疏忽。尝试在Oracle开发者邮件列表上提出这个问题,并看看官方的回答是什么。如果这是一个疏忽,Sun/Oracle一直非常不愿意在发布后进行API更改。这可能意味着它最初是一个疏忽,进入了发布版本,现在只是被冻结在时间中。 - Chris K
8
请参见Bug 4391558 - RealSkeptic
2个回答

6

RealSkeptic在上面的评论中链接了JDK-4391558。该bug中的评论解释了原因:

覆盖equals(Object)的问题在于您还必须覆盖hashCode()以返回一个值,该值保证仅当两个对象的哈希码也相等时,equals()才为true。

但是:

这里的问题在于Area.equals(Area)不执行非常直接的比较。它费力地检查两个区域中的每个几何图形,并测试它们是否覆盖相同的封闭空间。两个Area对象可能对同一封闭空间具有完全不同的描述,而equals(Area)将检测到它们是相同的。

因此,我们基本上只剩下一些不太愉快的选择,例如:

弃用equals(Area)并创建替代名称,例如“areasEqual”,以避免混淆。不幸的是,旧方法将保留并且可以链接,并且会陷入许多打算调用equals(Object)版本的人。
或者:
弃用equals(Area),并将其实现更改为与equals(Object)完全相同,以避免如果调用错误的方法而导致语义问题。创建一个具有不同名称的新方法以避免混淆,以实现equals(Area)提供的旧功能。
或者:
实现equals(Object)以调用equals(Area),并实现一个虚拟的hashCode(),以通过返回常量的方式以一种退化的方式尊重equals / hashCode合同,使hashCode方法基本无用,并使区域对象在HashMap或Hashtable中几乎无用。
或其他修改equals(Area)行为的方法,这些方法将更改其语义或使其与hashCode不一致。
看起来维护者认为更改这种方法既不可行(因为在错误评论中概述的两个选项都不能完全解决问题),也不重要(因为该方法的实现相当缓慢,可能只会在将一个Area实例与其本身进行比较时返回true,正如评论者所建议的那样)。

0

为什么Java的Area#equals方法没有覆盖Object#equals方法呢?

因为对于参数类型不同的重载方法,覆盖是不必要的。

重写的方法应该与父类中的方法具有相同的方法名称、返回类型、参数数量和参数类型,唯一的区别在于方法的定义。

这种情况并不强制我们进行覆盖,但是它遵循以下规则:

1.)方法的参数数量不同。

2.)参数类型不同(例如将float类型的参数更改为int类型的参数)。

"为什么他们不用不会与等号方法这样基本概念产生冲突的方式来命名可疑的方法呢?"

因为这可能会在未来困扰人们。如果我们有一个时间机器到90年代,我们可以做到而不担心这个问题。


2
你误解了他的问题。问题是为什么要重载它而不是覆盖它。@RealSkeptic提供的错误链接提供了一些见解。 - Deepak Bala
@DeepakBala 我解释了为什么你不应该覆盖它,以及这个概念如何遵循重载的规则。不确定你的意思,因为我觉得我已经回答了问题,你能否澄清一下? - BAR
抱歉,但正如我在问题中所述,我非常清楚什么是覆盖和重载以及何时可以使用或不使用每个选项。但正如@DeepakBala所指出的那样,这完全没有回答我的问题。我可以看到(并理解)现状,但想知道为什么最初会做出这种看似有问题的决定。 - Frank
@Frank 我认为这是有道理的。"因为在参数类型不同的重载方法中,覆盖是不必要的。" 这是先验条件,还有什么问题吗?从逻辑上讲,这个决定可能在清晰度方面实现得更好,但决策并没有错误。我错了吗? - BAR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接