什么问题/陷阱,必须重写时,必须考虑equals
和hashCode
?
equals()
(javadoc)必须定义等价关系(它必须是自反,对称和传递).此外,它必须是一致的(如果对象未被修改,则它必须保持返回相同的值).此外,o.equals(null)
必须始终返回false.
hashCode()
(javadoc)也必须是一致的(如果对象没有被修改equals()
,它必须保持返回相同的值).
这两种方法之间的关系是:
无论何时
a.equals(b)
,a.hashCode()
必须与之相同b.hashCode()
.
如果你覆盖一个,那么你应该覆盖另一个.
使用您用于计算的同一组字段equals()
进行计算hashCode()
.
使用优秀的辅助类EqualsBuilder和HashCodeBuilder从阿帕奇共享郎库.一个例子:
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(); } }
使用基于散列的集合或映射(如HashSet,LinkedHashSet,HashMap,Hashtable或WeakHashMap)时,请确保放入集合中的键对象的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()
迫使装载并返回"李四".根据我的经验,这种情况经常发生在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
就会发生变化.如果对象在a中HashSet
,你将"永远不会"再次找到它.
在我的Person
例子中,我可能会使用getName()
for hashCode
和getId()
plus 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且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
(因为你扩展它),在这种情况下你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); } }
请注意,如果要满足Liskov替换原则,则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())
.
我在final
equals实现中使用#1 ,或者在实现为equals规定算法的接口时(比如java.util
集合接口 - 正确的检查方式(obj instanceof Set)
或正在实现的任何接口).当可以覆盖equals时,这通常是一个糟糕的选择,因为这会破坏对称属性.
选项#2允许安全地扩展类,而不会覆盖等于或破坏对称性.
如果你的班级也是Comparable
,那么equals
和compareTo
方法也应该是一致的.这是Comparable
类中equals方法的模板:
final class MyClass implements Comparable{ … @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的秘密由安格兰格.我非常爱它.她也是关于Java中泛型的一个很棒的常见问题.在这里查看她的其他文章(向下滚动到"Core Java"),她还继续使用Part-2和"混合类型比较".玩得开心吧!
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集合.