15

它在 Object 的.equals(Object)javadoc 中说明:

它是对称的:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应该返回 true。

在示例代码的几乎所有地方,我都看到了用作第一个测试之一的覆盖.equals(Object)方法instanceof,例如这里:覆盖equals和hashCode时必须考虑哪些问题/陷阱?

public class Person {
    private String name;
    private int age;

    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (obj == this)
            return true;
        if (!(obj instanceof Person))
            return false;
        ...
    }

}

现在有了class SpecialPerson extends Personin equals

        if (!(obj instanceof SpecialPerson))
            return false;

我们不保证这.equals()是对称的。例如,这里已经讨论过:any-reason-to-prefer-getclass-over-instanceof-when-generating-equals

Person a = new Person(), b = new SpecialPerson();

a.equals(b);    //sometimes true, since b instanceof Person
b.equals(a);    //always false

也许我应该在 SpecialPerson 的 equals 直接调用 super 的开头添加?

    public boolean equals(Object obj) {
        if( !obj instanceof SpecialPerson )
            return super.equals(obj);
        ... 
        /* more equality tests here */
    }
4

5 回答 5

10

许多示例的使用instanceof有两个原因:a)它将空检查和类型检查合二为一或 b)该示例用于 Hibernate 或其他代码重写框架。

“正确”(根据 JavaDoc)解决方案是使用this.getClass() == obj.getClass(). 这适用于 Java,因为类是单例并且 VM 保证了这一点。如果你是偏执狂,你可以使用this.getClass().equals(obj.getClass()),但两者真的是等价的。

这在大多数情况下都有效。但有时,Java 框架需要用字节码做“聪明”的事情。这通常意味着他们会自动创建一个子类型。由于子类型应该被认为等于原始类型,equals()因此必须以“错误”的方式实现,但这并不重要,因为在运行时,子类型都将遵循某些模式。例如,他们会在调用 setter 之前做一些额外的事情。这对“平等”没有影响。

正如您所注意到的,当您同时拥有这两种情况时,事情开始变得丑陋:您确实扩展了基本类型,并将其与自动子类型生成混合在一起。如果你这样做,你必须确保你从不使用非叶子类型。

于 2013-09-02T14:51:24.740 回答
1

你在这里遗漏了一些东西。我将尝试强调这一点:

假设您有Person person = new Person()Person personSpecial = new SpecialPerson()然后我确定您不希望这两个对象相等。因此,它确实按要求工作,equal 必须返回 false。

此外,对称性指定equals()两个类中的方法必须同时服从它。如果一个等于返回真而另一个返回假,那么我会说缺陷在于等于覆盖。

于 2013-09-02T14:44:15.323 回答
0

您解决问题的尝试是不正确的。假设您有 2 个子类SpecialPersonBizarrePerson. 通过这种实现,BizarrePerson实例可以等于SpecialPerson实例。你通常不希望那样。

于 2013-09-02T14:38:13.060 回答
0

一个类型不应该认为自己等于任何其他类型的对象——即使是子类型——除非两个对象都派生自一个公共类,该类的契约规定了不同类型的后代应如何检查相等性

例如,抽象类StringyThing可以封装字符串,并提供方法来执行转换为字符串或提取子字符串等操作,但对支持格式没有任何要求。StringyThing例如,一个可能的子类型可能包含一个数组StringyThing并封装所有这些字符串的串联值。如果转换为字符串会产生相同的结果,则的两个实例将被定义为相等,并且类型彼此不知道的StringyThing两个否则无法区分的实例之间的比较可能不得不依赖于此,但是 -派生的类型可以包含代码以优化各种案例。例如,如果一个代表“字符的重复”,另一个代表“StringyThingStringyThingStringyThingMchN字符串 St" 的重复,而后一种类型知道第一种,它可以检查是否St只包含M/N字符的重复ch。这样的检查将指示字符串是否相等,而不必“扩展”任何一个其中。

于 2013-09-02T20:07:52.653 回答
0

不要使用instanceof. 改为使用this.getClass() == obj.getClass()。那么你正在检查这个确切的类。

与您一起工作时,equals也应始终使用hashCode并覆盖它!

Person 的 hashCode 方法可能如下所示:

@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

并在您的 equals 方法中像这样使用它:

if (this.hashCode() != obj.hashCode())
{
    return false;
}
于 2013-09-02T15:08:16.740 回答