//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());
}
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
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;
}
}
约书亚布洛赫在他的着作Effective Java Programming Language Guide(Addison-Wesley,2001)中声称"There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract." Tal不同意 .
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();
}
}
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
}
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替换原则不仅在 B 实例的情况下调用 super.equals(other) ,而且在 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()too .
11 回答
仍然惊讶于没有人推荐 Guava 库 .
我发现的一个问题是两个对象包含彼此的引用(一个示例是父/子关系,在父对象上使用便捷方法来获取所有子对象) .
例如,在进行Hibernate映射时,这些类型的东西是相当常见的 .
如果在hashCode或equals测试中包含关系的两端,则可能会进入以StackOverflowException结尾的递归循环 .
最简单的解决方案是不在方法中包含getChildren集合 .
对于equals,请查看 Secrets of Equals by Angelika Langer . 我非常爱它 . 关于 Generics in Java ,她也是一个很棒的常见问题解答 . 查看她的其他文章here(向下滚动到"Core Java"),她还继续使用Part-2和"mixed type comparison" . 玩得开心吧!
如果您正在处理使用像Hibernate这样的对象关系映射器(ORM)持久化的类,那么有些问题值得注意,如果您认为这已经不合理地复杂了!
Lazy loaded objects are subclasses
如果使用ORM持久保存对象,则在许多情况下,您将处理动态代理以避免从数据存储中过早加载对象 . 这些代理实现为您自己的类的子类 . 这意味着
this.getClass() == o.getClass()
将返回false
. 例如:如果您正在处理ORM,那么使用
o instanceof Person
是唯一能正常运行的东西 .Lazy loaded objects have null-fields
ORM通常使用getter来强制加载延迟加载的对象 . 这意味着如果
person
延迟加载,person.name
将为null
,即使person.getName()
强制加载并返回"John Doe" . 根据我的经验,这种情况在hashCode()
和equals()
中更频繁地出现 .如果您正在处理ORM,请确保始终使用getter,并且永远不要在
hashCode()
和equals()
中进行字段引用 .Saving an object will change its state
持久对象通常使用
id
字段来保存对象的键 . 首次保存对象时,此字段将自动更新 . 不要在hashCode()
中使用id字段 . 但你可以在equals()
中使用它 .我经常使用的模式是
但是:你不能在
hashCode()
中包含getId()
. 如果这样做,当对象被持久化时,它的hashCode
会发生变化 . 如果对象在HashSet
中,您将再次找到它 .在我的
Person
示例中,我可能会将getName()
用于hashCode
和getId()
加上getName()
(仅用于偏执)用于equals()
. 如果对于hashCode()
存在"collisions"的风险是可以的,但对于equals()
永远没有问题 .hashCode()
应使用equals()
中不变的属性子集在检查成员相等性之前,有两种方法可以检查类的相等性,我认为两种方法在适当的情况下都很有用 .
使用
instanceof
运算符 .使用
this.getClass().equals(that.getClass())
.我在
final
equals实现中使用#1,或者在实现一个为equals规定算法的接口时(比如java.util
集合接口 - 使用(obj instanceof Set)
或任何接口检查的正确方法,当等于时,你通常是一个不好的选择)被覆盖,因为它破坏了对称属性 .选项#2允许安全地扩展类,而不会覆盖等于或破坏对称性 .
如果您的类也是
Comparable
,则equals
和compareTo
方法也应该一致 . 这是Comparable
类中equals方法的模板:有关继承友好的实现,请查看Tal Cohen的解决方案,How Do I Correctly Implement the equals() Method?
摘要:
约书亚布洛赫在他的着作Effective Java Programming Language Guide(Addison-Wesley,2001)中声称"There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract." Tal不同意 .
他的解决方案是通过两种方式调用另一个非对称的blindlyEquals()来实现equals() . blindlyEquals()被子类覆盖,equals()被继承,并且永远不会被覆盖 .
例:
请注意,如果要满足Liskov Substitution Principle,则equals()必须在继承层次结构中起作用 .
从逻辑上讲,我们有:
a.getClass().equals(b.getClass()) && a.equals(b)
⇒a.hashCode() == b.hashCode()
但是 not 反之亦然!
理论(语言律师和数学倾向):
equals()
(javadoc)必须定义等价关系(它必须是自反,对称和传递) . 此外,它必须是一致的(如果未修改对象,则必须保持返回相同的值) . 此外,o.equals(null)
必须始终返回false .hashCode()
(javadoc)也必须一致(如果对象未按equals()
进行修改,则必须保持返回相同的值) .两种方法之间的 relation 是:
在实践中:
如果你覆盖一个,那么你应该覆盖另一个 .
使用相同的一组用于计算
equals()
以计算hashCode()
的字段 .使用Apache Commons Lang库中的优秀辅助类EqualsBuilder和HashCodeBuilder . 一个例子:
还要记住:
使用基于散列的Collection或Map(例如HashSet,LinkedHashSet,HashMap,Hashtable或WeakHashMap)时,请确保在对象位于集合中时,放入集合的键对象的hashCode()永远不会更改 . 确保这一点的防弹方法是使您的密钥不可变,which has also other benefits .
超类中有两种方法,如java.lang.Object . 我们需要将它们覆盖到自定义对象 .
只要它们相等,相等的对象必须产生相同的哈希码,但是不相等的对象不需要产生不同的哈希码 .
如果您想获得更多,请点击此链接为http://www.javaranch.com/journal/2002/10/equalhash.html
这是另一个例子,http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
玩得开心! @ . @
关于
obj.getClass() != getClass()
的澄清 .这句话是
equals()
继承不友好的结果 . JLS(Java语言规范)指定如果A.equals(B) == true
则B.equals(A)
也必须返回true
. 如果省略该语句,继承覆盖equals()
(并更改其行为)的类将破坏此规范 .请考虑以下示例,省略语句时会发生什么:
做
new A(1).equals(new A(1))
此外,new B(1,1).equals(new B(1,1))
结果给出了真实,应该如此 .这看起来非常好,但看看如果我们尝试使用这两个类会发生什么:
显然,这是错误的 .
如果要确保对称条件 . a = b如果b = a且Liskov替换原则不仅在
B
实例的情况下调用super.equals(other)
,而且在A
实例后检查:哪个会输出:
其中,如果
a
不是B
的引用,那么它可能是类A
的引用(因为你扩展它),在这种情况下你调用super.equals()
too .equals()方法用于确定两个对象的相等性 .
因为int值10总是等于10.但是这个equals()方法是关于两个对象的相等 . 当我们说对象时,它将具有属性 . 要确定相等性,请考虑这些属性 . 没有必要考虑所有属性来确定相等性,并且可以决定类定义和上下文 . 然后可以覆盖equals()方法 .
每当我们覆盖equals()方法时,我们应该总是覆盖hashCode()方法 . 如果没有,会发生什么?如果我们在应用程序中使用哈希表,它将不会按预期运行 . 由于hashCode用于确定存储的值的相等性,因此它不会为键返回正确的对应值 .
给定的默认实现是Object类中的hashCode()方法,它使用对象的内部地址并将其转换为整数并返回它 .
示例代码输出: