【问题标题】:Java .equals() instanceof subclass? Why not call superclass equals instead of making it final?Java .equals() instanceof 子类?为什么不调用超类equals而不是使其成为final?
【发布时间】:2013-09-05 16:26:47
【问题描述】:

Object的.equals(Object)javadoc中有说明:

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

在示例代码中的几乎所有地方我都看到覆盖了 .equals(Object) 方法,该方法使用 instanceof 作为第一个测试之一,例如这里:What issues / pitfalls must be considered when overriding equals and 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 Person 加入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 */
    }

【问题讨论】:

  • 在有效的java中讨论了类似的问题。你可以读一下。
  • 添加到 Manoose 评论 - Effective Java sais:“避免问题的最简单方法是不要重写 equals 方法,在这种情况下,每个实例都只等于它自己。这是正确的做法如果满足以下任一条件,请执行以下操作:[...] 超类已覆盖 equals,并且从超类继承的行为适用于此类。”

标签: java equals instanceof


【解决方案1】:

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

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

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

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

【讨论】:

  • 您能否引用文档中与您的第二段相符的部分?
  • @arshajii:它是类加载器合约的一部分。标准类加载器将始终为相同的类名返回相同的引用。当然,脚本类加载器的赌注是关闭的。
  • 哦,我的意思是“正确的解决方案是使用...”部分。你是说在equals() 中比较getClass()s 比使用instanceof 更好吗?
  • 是的。一般来说,instanceof 中的 instanceof 是错误的,因为它可以说子类型等于父类型,这并不总是正确的。在 Eclipse 错误中对此进行了长时间的讨论,因为 Hibernate 人员不断抱怨“失踪”instanceof,而语言纯粹主义者则磨砺了他们的干草叉。
【解决方案2】:

你在这里遗漏了一些东西。我会尽量强调这一点:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 嗯,在很多情况下,将两个不同子类的实例视为相等是有意义的。以List 为例。 ArrayList 是并且应该等于包含相同元素的LinkedList
    • 但是你可能不想在子类中覆盖equals,这就是这个问题的全部内容。
    【解决方案4】:

    不要使用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;
    }
    

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 2011-09-12
        • 2013-07-05
        • 1970-01-01
        • 2015-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-25
        相关资源
        最近更新 更多