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个子类SpecialPerson
和BizarrePerson
。 通过此实现, 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
。 这样的检查将指示字符串是否相等,而不必“扩展”它们中的任何一个。