Java是很多开发者都曾经接触过的一门开发语言,它之所以流行,那是因为他有很多的设计思想解决了很多现实中的问题,其中对象的equals方法,hashCode方法的设计思想值得我们学习,所以我们有必要去深入学习一下这两个方法。
1:hashCode的作用
hashcode
的作用大概归纳为如下几点:
-
hashCode
的存在主要用于查找的快捷性,如Hashtable
,HashMap
等,hashCode
是用来在散列存储结构中确定对象的存储地址的。 -
如果两个对象相同,就是适用于
equals(java.lang.Object)
方法,那么这两个对象的hashCode
一定要相同。 -
如果对象的
equals
方法被重写,那么对象的hashCode
也尽量重写,并且产生hashCode
使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点。 -
两个对象的
hashCode
相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object)
方法,只能够说明这两个对象在散列存储结构中,如Hashtable
,他们“存放在同一个篮子里”。
1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,
如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,
然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,
9除8的余数为1,那么我们就把该类存在1这个位置,
如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。
这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。
2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),
例如9除以8和17除以8的余数都是1,那么这是不是合法的,
回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,
但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,
你不通过重写hashcode()来找到桶,光重写equals()有什么用啊
上述回答转载于:Java中hashCode的作用 。由于作者总结的太好,所以直接转载了,希望对你有所帮助。
看看Object
对象实例hashCode
代码如下:
package java.lang;
public class Object {
·······
/**
* 返回该对象的哈希码值。
* 支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能
* {@link java.util.HashMap}.
* <p>
* hashCode 的常规协定是:
* <ul>
* <li>在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,
* 必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。
* 从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
* <li>如果根据 equals(Object) 方法,两个对象是相等的,
* 那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
* <li>如果根据 equals(java.lang.Object) 方法,两个对象不相等,
* 那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。
* 但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
* </ul>
* <p>
* 实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
* (这一般是通过将该对象的内部地址转换成一个整数来实现的,
* 但是 JavaTM 编程语言不需要这种实现技巧。)
*
* @return## 标题 此对象的一个哈希码值。
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
·······
}
2:如何理解equals(Object obj)方法,它和 == 操作符相比,有什么区别?
-
== 操作符分为两种情况
- 比较基础类型
(byte,short,int,long,float,double,char,boolean)
时,比较的是值是否相等 - 比较对象,比较的是对象在内存中的空间地址是否相等。
- 比较基础类型
-
equals(Object obj)
方法比较也分为两种情况:- 如果一个类没有重写
equals(Object obj)
方法,则等价于通过==
比较两个对象,即比较的是对象在内存中的空间地址是否相等。 - 如果重写了
equals(Object obj)
方法,则根据重写的方法是内容去比较是否相等,返回true
则相等,false
则不相等。
- 如果一个类没有重写
3: 如果您去重写equals(Object obj)方法,您会怎么做?重写的过程又需要注意什么?
我会遵守JAVA官方的通用约定,根据官方实例代码,简述约定流程如下:
-
自反性:对于非
null
的对象obj
,必须有obj.equals(obj)=true
; -
对称性:如果
objx.equals(objy)=true
,那么objy.equals(objx)
必须也为true
; -
传递性:如果
objx.equals(objy)=true
而且objy.equals(objz)=true
,那么objx.equals(objz)
必须为true
; -
对于非
null
的对象obj
,一定有obj.equals(null)=false
; -
当
equals(Object obj)
方法被重写时,通常有必要重写hashCode
方法,以维护hashCode
方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
根据以上约定我们就可以按照如下步骤去重写equals
方法。
重写equals方法步骤如下:
- 先使用
==
操作符判断两个对象的引用地址是否相同。 - 使用
instanceof
来判断 两个对象的类型是否一致。 - 如果类型相同,则把待比较参数转型,逐一比较两个对象内部的值是否一致,全部一致才返回
true
,否则返回false
。 - 重写
hashCode
方法,确保相等的两个对象必须具有相等的哈希码
注意:
我们在重写一个类的
hashCode
方法时,最好是将所有用于相等性检查的字段都进行hashCode
计算,最后将所有hashCode
值相加,得出最终的hashCode
,这样可以保证hashCode
生成均匀,不容易产生碰撞。
常见数据类型hashcode计算方式如下:
重要字段var的类型 | hash运算 |
---|---|
byte,short,int,char | (int)var |
long | (int)(var ^ (var >>> 32)) |
float | Float.floatToIntBits(var) |
double | long bits = Double.doubleToLongBits(var);分量 = (int)(bits ^ (bits >>> 32)) |
引用类型 | (null == var ? 0 : var.hashCode()) |
示例代码:
/**
* 指示其他某个对象是否与此对象“相等”。
* <p>
* equals 方法在非空对象引用上实现相等关系:
* <ul>
* <li>自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
*
* <li>对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,
* x.equals(y) 才应返回 true。
*
* <li>传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,
* 并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
*
* <li>一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回
* true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
*
* <li>对于任何非空引用值 x,x.equals(null) 都应返回 false。
* </ul>
*
* <p>
* Object 类的 equals 方法实现对象上差别可能性最大的相等关系;
* 即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,
* 此方法才返回 true(x == y 具有值 true)。
*
* <p>
* 注意:当此方法被重写时,通常有必要重写 hashCode 方法,
* 以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
*
* @param 要与之比较的引用对象。
* @return 如果此对象与 obj 参数相同,则返回 true;否则返回 false。
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
4.如果要您去维护一个类的hash散列表,您会如何设计,如何解决hash冲突?
我们在设计类的hash
散列表时,不能保证每个元素的hash
值都是不一样的,这样就会造成hash
冲突。解决hash
冲突有如下4种方法:
-
开发定址法:既然当前位置容不下冲突的元素了,那就再找一个空的位置存储
Hash
冲突的值(当前index
冲突了,那么将冲突的元素放在index+1
)。 -
再散列法:换一个
Hash
算法再计算一个hash
值,如果不冲突了就存储值(例如第一个算法是名字的首字母的Hash
值,如果冲突了,计算名字的第二个字母的Hash
值,如果冲突解决了则将值放入数组中)。 -
链地址法:每个数组中都存有一个单链表,发生
Hash
冲突时,只是将冲突的value
当作新节点插入到链表(HashMap
解决冲突的办法)。 -
公共溢出区法:将冲突的
value
都存到另外一个顺序表中,查找时如果当前表没有对应值,则去溢出区进行顺序查找。
5.总结
-
当你真要的需要重写equals方法,这两点一定要记住:
-
A.如果两个对象相等(
equals
() 返回true
),那么它们的hashCode
()一定要相同; -
B.如果两个对象
hashCode
()相等,它们并不一定相等(equals
() 不一定返回true
)。
-
-
如果重写的
equals
方法但不重写hashCode
,都是耍流氓,会有意想不到的结果。 -
重写
hashCode
方法时,尽可能将所有用于相等比较的参数都参与hashCode
的计算。 -
建立
hash
散列表的意义就是在于,提高查询效率,当数据量大时,尤为显著。
参考资料: