Java .equals()instanceof子类? 为什么不调用超类等于而不是最终?

它在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 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的开头添加等于直接调用super?

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

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

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

这大部分时间都有效。 但有时,Java框架需要使用字节代码做“聪明”的事情。 这通常意味着它们会自动创建子类型。 由于子类型应该被认为与原始类型相同,所以equals()必须以“错误”的方式实现,但这并不重要,因为在运行时,子类型都将遵循某些模式。 例如,他们会在调用setter之前执行其他操作。 这对“平等”没有影响。

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

你在这里遗漏了一些东西。 我会试着强调一下:

假设你有Person person = new Person()Person personSpecial = new SpecialPerson()那么我相信你不会希望这两个对象是平等的。 所以,它真的按要求工作,等于必须返回false。

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

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

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

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

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