equals与hashCode关系梳理
这个并不是一个通用性编程问题,只属于在Java领域内专有问题。
要做好心理准备,这是一个复杂类的问题,要解答这个问题,需要梳理清楚两个函数和其它类之间的关系,并且它们之间的关系有点交织。
equals用法
在 Object 类中还包含了 equals() 方法:
public boolean equals(Object obj) {
return (this == obj);
}
说明:
- == 用于比较
变量
所对应的内存中所存储的
数值
是否相同,要比较
两个基本类型的数据(注意是基本类型)
或
两个引用变量
是否相等,只能用==操作符。
hashCode用法
在 Object 类中还包含了 hashCode() 方法:
public native int hashCode();
请回答,为什么 Object 类需要一个 hashCode() 方法呢?
在 Java 中,hashCode() 方法的主要作用就是为了配合哈希表使用的。
哈希表(Hash Table),也叫散列表,是一种可以通过关键码值(key-value)直接访问的数据结构,它最大的特点就是可以快速实现查找、插入和删除。其中用到的算法叫做哈希,就是把任意长度的输入,变换成固定长度的输出,该输出就是哈希值。像 MD5、SHA1 都用的是哈希算法。
像 Java 中的 HashSet、Hashtable、HashMap 都是基于哈希表的具体实现。其中的 HashMap 就是最典型的代表。
大家想一下,
如果没有哈希表,但又需要这样一个数据结构,它里面存放的数据是不允许重复的,它是怎么实现的?
- √ 要不使用 equals() 方法进行逐个比较?这种方案当然是可行的。
但如果数据量特别特别大,采用 equals() 方法进行逐个对比的效率肯定很低很低,总结:能解决,但效率不高。 - √√ 最好的解决方案就是哈希表。总结:不光能解决,还效率不高。
案例说明:
拿 HashMap 来说吧,当我们要在它里面添加对象时,先调用这个对象的 hashCode() 方法,得到对应的哈希值,然后将哈希值和对象一起放到 HashMap 中。当我们要再添加一个新的对象时:
- 获取对象的哈希值;
- 和之前已经存在的哈希值进行比较,如果不相等,直接存进去;
- 如果有相等的,再调用 equals() 方法进行对象之间的比较,如果相等,不存了;
- 如果不等,说明哈希冲突了,增加一个链表,存放新的对象;
- 如果链表的长度大于 8,转为红黑树来处理。
就这么一套下来,调用 equals() 方法的频率就大大降低了。也就是说,只要哈希算法足够的高效,把发生哈希冲突的频率降到最低,哈希表的效率就特别的高。
总结
== 用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据(注意是基本类型)或两个 引用变量是否相等,只能用==操作符。
equals 比较的是
值和地址
,如果没有重写equals方法,其作用与==相同;
在String类中,重写了equals方法,比较的是
值
是否相等;
hashCode用于散列数据结构中的
hash值计算
;
equals两个对象相等,那hashcode一定相等,hashcode相等,不一定是同一个对象(hash冲突现象);
hashCode 一般与 equals 一起使用,两个对象作「相等」比较时,因判断 hashCode 是判断 equals 的
先决条件
.
为什么一个类中需要两个比较方法
因为重写的 equals() 里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个 hash 值进行比较就可以了,效率很高,那么 hashCode() 既然效率这么高为什么还要 equals() 呢?
因为 hashCode() 并不是完全可靠,有时候不同的对象他们生成的 hashcode 也会一样(hash冲突),所以 hashCode()只能说是大部分时候可靠,并不是绝对可靠。
equals() 相等的两个对象他们的 hashCode() 肯定相等,也就是用 equals() 对比是绝对可靠的。
为什么重写 equals 方法时必须同时重写 hashCode 方法?
可以先看看Java这B 给出的一些建议,就是事前就规定好了...
public class Object {
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* `java.util.HashMap`.
*
* The general contract of `hashCode` is:
*
* a) Whenever it is invoked on the same object more than once during
* an execution of a Java application, the `hashCode` method must
* consistently return the same integer, provided no information
* used in `equals` comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
*
* b) If two objects are equal according to the `equals(Object)` method,
* then calling the `hashCode` method on each of the two objects must
* produce the same integer result.
*
* c) It is not required that if two objects are unequal according to the
* `equals(Object)` method, then calling the `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.
*/
@IntrinsicCandidate
public native int hashCode();
/**
* Indicates whether some other object is "equal to" this one.
*
* @apiNote
* It is generally necessary to override the `hashCode` method whenever this
* method is overridden, so as to maintain the general contract for the `hashCode`
* method, which states that equal objects must have equal hash codes.
*/
public boolean equals(Object obj) {
return (this == obj);
}
}
上面介绍了 hashCode 方法注释上列出的三个通用约定,equals 方法的注释上也有这么一句话:「每当重写 equals 方法时,都需要重写 hashCode 方法,这样才没有破坏 hashCode 方法的通用约定,即:两个对象为 Equal 的话(调用 equals 方法为 true), 那么这两个对象分别调用 hashCode 方法也需要返回相同的哈希值」。
所以只重写 equals 方法不重写 hashCode 方法的话,可能会造成两个对象调用 equals 方法为 true,而 hashCode 值不同的情形,这样即可能造成异常的行为。
这个情形是什么?
两个内容相等的Person对象p1和p2的hashCode()不同,是因为在Person类中没有重写hashCode()方法,它们使用的是Object类继承下来的hashCode()方法的默认实现。
在Object类中,hashCode()方法的默认实现是将对象的内存地址值作为哈希码返回。
总结:
就是一个约定而已。也是为了逻辑的自洽。
Reference
Java hashCode方法深入解析
https://www.javabetter.cn/basic-extra-meal/hashcode.html
Java:为什么重写 equals 方法时必须同时重写 hashCode 方法?
https://leileiluoluo.com/posts/always-override-hashcode-when-override-equals.html