覆盖equals
和hashCode
时必须考虑哪些问题/陷阱?
答案 0 :(得分:1412)
equals()
(javadoc)必须定义等价关系(它必须是自反,对称和传递)。此外,它必须一致(如果未修改对象,则必须保持返回相同的值)。此外,o.equals(null)
必须始终返回false。
hashCode()
(javadoc)也必须一致(如果对象未按equals()
进行修改,则必须保持返回相同的值)
两种方法之间的关系是:
每当
a.equals(b)
,a.hashCode()
必须与b.hashCode()
相同。
如果你覆盖一个,那么你应该覆盖另一个。
使用您用于计算equals()
的同一组字段来计算hashCode()
。
使用EqualsBuilder库中优秀的帮助程序类HashCodeBuilder和Apache Commons Lang。一个例子:
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();
}
}
使用基于散列的Collection或Map时,例如HashSet,LinkedHashSet,HashMap,Hashtable或{{3确保当对象在集合中时,放入集合的键对象的hashCode()永远不会更改。确保这一点的防弹方法是使您的密钥不可变,WeakHashMap。
答案 1 :(得分:287)
如果您正在处理使用像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
字段来保存对象的键。首次保存对象时,该字段将自动更新。请勿在{{1}}中使用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()
答案 2 :(得分:83)
关于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且Liskov替换原则不仅在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
的引用(因为你扩展它),在这种情况下你调用{ {1}} 太。
答案 3 :(得分:44)
有关继承友好的实现,请查看Tal Cohen的解决方案How Do I Correctly Implement the equals() Method?
要点:
在他的书Effective Java Programming Language Guide(Addison-Wesley,2001)中,约书亚布洛赫声称“在保留平等合同的同时,根本无法扩展可实例化的类并添加方面。”塔尔不同意。
他的解决方案是通过两种方式调用另一个非对称的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);
}
}
请注意,如果要满足Liskov Substitution Principle,则equals()必须在继承层次结构中起作用。
答案 4 :(得分:31)
仍然惊讶于没有人推荐番石榴图书馆。
//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());
}
答案 5 :(得分:26)
超类中有两种方法作为java.lang.Object。我们需要将它们覆盖为自定义对象。
public boolean equals(Object obj)
public int hashCode()
Equal对象必须产生相同的哈希码,只要它们相等,但是不对等的对象不需要产生不同的哈希码。
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
玩得开心! @。@
答案 6 :(得分:18)
在检查成员相等性之前,有两种方法可以检查类的相等性,我认为两种方法在适当的情况下都很有用。
instanceof
运算符。this.getClass().equals(that.getClass())
。我在final
equals实现中使用#1,或者在实现规定equals算法的接口时(如java.util
集合接口 - 与{{1}一起检查的正确方法)或者你正在实现的任何接口)。当可以覆盖equals时,这通常是一个糟糕的选择,因为这会破坏对称属性。
选项#2允许安全地扩展类,而不会覆盖等于或破坏对称性。
如果您的课程也是(obj instanceof Set)
,则Comparable
和equals
方法也应保持一致。这是compareTo
类中equals方法的模板:
Comparable
答案 7 :(得分:15)
对于等号,请按Secrets of Equals查看 Angelika Langer 。我非常爱它。她也是关于 Generics in Java 的精彩常见问题解答。查看她的其他文章here(向下滚动到“核心Java”),她还继续使用第2部分和“混合类型比较”。读它们玩得开心!
答案 8 :(得分:11)
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
答案 9 :(得分:7)
逻辑上我们有:
a.getClass().equals(b.getClass()) && a.equals(b)
⇒a.hashCode() == b.hashCode()
但是不是反之亦然!
答案 10 :(得分:6)
我发现的一个问题是两个对象包含彼此的引用(一个示例是父/子关系,在父项上使用方便方法来获取所有子项)。
例如,在进行Hibernate映射时,这些事情很常见。
如果在hashCode或equals测试中包含关系的两端,则可能会进入以StackOverflowException结尾的递归循环。
最简单的解决方案是不在方法中包含getChildren集合。