为什么重写equals必须重写hashCode
1. Object中equals、hashCode
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
首先看一下equals
方法,Object中默认比较两个对象的地址。也就是说如果一个类默认的equals
,就比较地址。而反观hashCode
方法,我们先来看一下hashCode
注释
/**
* Returns a hash code value for the object.
//返回的是一个对象的hash值
* This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer,
//这里又是关键,无论调用多少次hashCode,同一个对象都会返回同一个hash值
* provided no information
* used in {@code equals} comparisons on the object is modified.
// 这里没看懂 但是测试了一下,如果同一个对象属性变化了,它的hash值也会不变
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@code equals(Object)}
* method, then calling the {@code hashCode} method on each of
* the two objects must produce the same integer result.
//如果equals比较两个对象相等,那么这两个对象必须就有相同的hash值
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the {@code hashCode} method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
再结合Effective Java 第三版
中所提到的
每个覆盖了equals方法的类中,必须覆盖hashCode。如果不这么做,就违背了hashCode的通用约定,也就是上面注释中所说的。进而导致该类无法结合所以与散列的集合一起正常运作,这里指的是HashMap、HashSet、HashTable、ConcurrentHashMap。
2. equals和hashCode同时存在的意义
equals
和hashCode
都是用来判断两个对象想不想等的,那么问题来了?
为什么需要两个呢?
- equals - 保证比较对象是否是绝对相等的
- hashCode - 保证在最快的时间内判断两个对象是否相等,可能有误差值
一个是保证可靠,一个是保证性能。也就是说:
- 同一个对象的hashCode一定相等,不同对象的hashCode也可能相等,这是因为hashCode是根据地址hash出来的一个int 32 位的整型数字,相等是在所难免。
- equals比较的是两个对象的地址,同一个对象地址肯定相同,不同的对象地址一定不同,可靠性是这么来的。
就像HashMap里面插入时判断:
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
判断两个key是否相同逻辑
- 先比较 hash (通过hashCode的高16位和低16位进行异或运算得出的) ,因为两个相同的对象hash值一定相等。
- 再比较两个对象的地址是否相同,== 判断是否绝对相等,而equals判断是否客观相等。
下面分析一下简单的不重写hashCode的后果和在存到HashMap中可能出现的后果
3. 如果只重写equals的后果
- 无论是
Effective Java
,还是阿里巴巴Java规范手册
都是要求重写equals,必须重写hashCode。 - 两个相等的对象必须具有相等的散列码(Java关键约定)
那么不重写的后果是什么呢?
举一个例子: 如果一个只重写了equals(比较所有属性是否相等)的类 new 出了两个属性相同的对象。这时可以得到的信息是这个属性相同的对象地址肯定不同,但是equals是true,hashCode返回的是不相等的(一般不会出现hash碰撞)。
也就是说这个类对象违背了Java对于两个对象相等的约定。违背约定的原因是 可靠的equals判断两个对象是相等的,但是他们两个的散列码确是不相等的。
总结来说:
- equals 为 true , hashCode 必须相等
- hashCode 相等时 , equals 可以不用为 true (也就是hash碰撞的时候)
4 . 不重写HashCode对于散列表的影响
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
相同的场景,相同配方:
两个所有属性都相等的对象,但是地址不同。没重写hashCode
时,p.hash == hash
一定不相等。但是逻辑上这两个对象是相等的,并且equals
也是相等的。 这就会导致,HashMap里面本来有这个key,但是你告诉我没有,导致了put操作成功。逻辑上是不符合规范的,get时取出来的也可能是自己另一个的value。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。