这个并不是一个通用性编程问题,只属于在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()方法的默认实现是将对象的内存地址值作为哈希码返回。两个内容同样的对象,但是地址会不同,这样就会返回false,导致异常。
总结:
就是一个前人约定而已。也是为了逻辑的自洽。
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