在Java中重写equals和hashCode时应该考虑哪些问题?
题
覆盖时必须考虑哪些问题/陷阱 equals
和 hashCode
?
解决方案
理论(对于语言律师和数学爱好者):
equals()
(javadoc)必须定义一个等价关系(它必须是 反射性的, 对称的, , 和 及物的)。此外,还必须是 持续的 (如果对象没有被修改,那么它必须保持返回相同的值)。此外, o.equals(null)
必须始终返回 false。
hashCode()
(javadoc)也必须是 持续的 (如果对象没有被修改 equals()
, ,它必须保持返回相同的值)。
这 关系 两种方法之间是:
每当
a.equals(b)
, , 然后a.hashCode()
必须与b.hashCode()
.
在实践中:
如果您覆盖其中一个,那么您应该覆盖另一个。
使用与计算相同的一组字段 equals()
计算 hashCode()
.
使用优秀的帮助类 等于生成器 和 哈希码生成器 来自 阿帕奇通用语言 图书馆。一个例子:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
还请记住:
当使用基于哈希的 收藏 或者 地图 例如 哈希集, 链接哈希集, 哈希映射, 哈希表, , 或者 弱哈希映射, ,确保放入集合中的关键对象的 hashCode() 在该对象位于集合中时不会改变。确保这一点的万无一失的方法是让你的密钥不可变, 这还有其他好处.
其他提示
如果您正在处理使用像 Hibernate 这样的对象关系映射器 (ORM) 持久化的类,并且您不认为这已经变得不合理的复杂,那么有一些问题值得注意!
延迟加载对象是子类
如果您的对象使用 ORM 进行持久化,则在许多情况下您将处理动态代理以避免过早从数据存储中加载对象。这些代理作为您自己的类的子类来实现。这意味着this.getClass() == o.getClass()
将返回 false
. 。例如:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
如果您正在处理 ORM,请使用 o instanceof Person
是唯一能够正确运行的东西。
延迟加载的对象具有空字段
ORM 通常使用 getter 来强制加载延迟加载的对象。这意味着 person.name
将 null
如果 person
是延迟加载的,即使 person.getName()
强制加载并返回“John Doe”。根据我的经验,这种情况更常见于 hashCode()
和 equals()
.
如果您正在处理 ORM,请确保始终使用 getter,并且切勿在 hashCode()
和 equals()
.
保存对象会改变其状态
持久化对象经常使用 id
字段来保存对象的键。首次保存对象时,此字段将自动更新。不要使用 id 字段 hashCode()
. 。但你可以用它 equals()
.
我经常使用的一个模式是
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
但:你不能包括 getId()
在 hashCode()
. 。如果这样做,当一个对象被持久化时,它的 hashCode
变化。如果该对象位于 HashSet
, ,你将“永远”找不到它了。
在我的 Person
例如,我可能会使用 getName()
为了 hashCode
和 getId()
加 getName()
(只是为了偏执) equals()
. 。如果有一些“碰撞”的风险也没关系 hashCode()
, ,但永远不适合 equals()
.
hashCode()
应该使用来自的不变的属性子集 equals()
关于的澄清 obj.getClass() != getClass()
.
该声明的结果是 equals()
继承不友好。JLS(Java 语言规范)规定,如果 A.equals(B) == true
然后 B.equals(A)
也必须返回 true
. 。如果您省略该语句继承覆盖的类 equals()
(并改变其行为)将破坏此规范。
考虑以下示例,了解省略该语句时会发生什么:
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
正在做 new A(1).equals(new A(1))
还, new B(1,1).equals(new B(1,1))
结果给出了正确的结果,正如它应该的那样。
这看起来非常好,但是看看如果我们尝试使用这两个类会发生什么:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
显然,这是错误的。
如果要保证对称条件。a=b 如果 b=a 并且调用里氏替换原理 super.equals(other)
不仅在以下情况 B
实例,但之后检查 A
实例:
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
这将输出:
a.equals(b) == true;
b.equals(a) == true;
哪里,如果 a
不是参考 B
, ,那么它可能是类的引用 A
(因为你扩展了它),在这种情况下你调用 super.equals()
也.
对于继承友好的实现,请查看 Tal Cohen 的解决方案, 如何正确实现equals()方法?
概括:
在他的书中 有效的 Java 编程语言指南 (Addison-Wesley,2001年),约书亚·布洛赫(Joshua Bloch)声称:“根本没有办法扩展一个可靠的班级并在保留平等合同的同时添加方面。”塔尔不同意。
他的解决方案是通过双向调用另一个非对称的blindlyEquals()来实现equals()。blindlyEquals() 被子类重写,equals() 被继承,并且永远不会被重写。
例子:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
请注意,如果以下情况,equals() 必须跨继承层次结构工作: 里氏替换原则 是要得到满足。
仍然令人惊讶的是没有人为此推荐番石榴库。
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
超类 java.lang.Object 中有两个方法。我们需要将它们重写为自定义对象。
public boolean equals(Object obj)
public int hashCode()
只要它们相等,相等的对象就必须产生相同的哈希码,但是不相等的对象不需要产生不同的哈希码。
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
如果您想获得更多信息,请检查此链接 http://www.javaranch.com/journal/2002/10/equalhash.html
这是另一个例子,http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
玩得开心!@.@
在检查成员相等性之前,有几种方法可以检查类相等性,我认为这两种方法在适当的情况下都很有用。
- 使用
instanceof
操作员。 - 使用
this.getClass().equals(that.getClass())
.
我在a中使用#1 final
equals 实现,或者实现规定 equals 算法的接口时(例如 java.util
集合接口——检查的正确方法 (obj instanceof Set)
或您正在实现的任何接口)。当 equals 可以被覆盖时,这通常是一个糟糕的选择,因为这会破坏对称性。
选项 #2 允许安全地扩展类,而不会覆盖 equals 或破坏对称性。
如果你的班级也是 Comparable
, , 这 equals
和 compareTo
方法也应该一致。这是 equals 方法的模板 Comparable
班级:
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
equals()方法用于判断两个对象是否相等。
因为 int 值 10 始终等于 10。但这个 equals() 方法是关于两个对象相等的。当我们说对象时,它就会有属性。为了决定平等,需要考虑这些属性。不必考虑所有属性来确定相等性,并且可以根据类定义和上下文来决定。然后可以重写 equals() 方法。
每当我们重写 equals() 方法时,我们都应该重写 hashCode() 方法。如果没有,会发生什么?如果我们在应用程序中使用哈希表,它将不会按预期运行。由于 hashCode 用于确定存储的值的相等性,因此它不会返回键的正确对应值。
给出的默认实现是 Object 类中的 hashCode() 方法,使用对象的内部地址并将其转换为整数并返回。
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
示例代码输出:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
从逻辑上我们有:
a.getClass().equals(b.getClass()) && a.equals(b)
⇒ a.hashCode() == b.hashCode()
但 不是 反之亦然!
我发现的一个问题是两个对象包含彼此的引用(一个例子是父/子关系,在父对象上使用一种方便的方法来获取所有子对象)。
例如,在进行 Hibernate 映射时,此类事情相当常见。
如果您在 hashCode 或 equals 测试中包含关系的两端,则可能会进入以 StackOverflowException 结束的递归循环。
最简单的解决方案是不在方法中包含 getChildren 集合。