关于 equals() 与 hashCode() 个人理解总结
1:如果我们自定义的类没有重写 hashCode 和 equals() 方法的话 ?
在使用 equals 对比的时候,是错误的!
因为没有重写的类都是继承祖宗 Object 的 hashCode 和 equals 方法 , 这是Object 源码的 hashCode 和 equals 方法
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
hashCode 方法有 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C== 语言实现的,由java去调用.
(对于还不理解hashCode 的人来说,先断言 Object 类 hashCode 返回的就是对象的存储地址,有助于理解)
equals 方法直接对比的是 对象的内存地址 , 跟内容毫无关系!!!
实战案例:
package com.example;
import lombok.Data;
import org.junit.Test;
import java.util.*;
/**
* @Author: qiuj
* @Description:
* @Date: 2019年8月6日17:22:06
*/
public class HashCodeTest {
@Test
public void test4(){
HashSet hashSet = new HashSet();
hashSet.add(new Handsome(18,"邱健"));
hashSet.add(new Handsome(18,"邱健"));
Iterator iterator = hashSet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
class Handsome{
int age;
String name;
public Handsome(int age,String name){
this.age = age;
this.name = name;
}
public String toString(){
return age + ":" +name;
}
}
}
我们用 Set 集合,集合内的元素不重复。结果返回false 。就是因为 Handsome 类没有重写 hashCode 和 equals 方法
set 在添加第二个对象时,先把 hashCode 算出来 ,发现容器内并没有此 hashCode 的值。直接就放入容器内了。此时都还没有进行 equals 的对比 。
深入 hashCode 底层
实际上:hashCode 不一定都是返回对象的存储地址,但是有些JVM在实现时是直接返回对象的存储地址,但是大多时候并不是这样,只能说可能存储地址有一定关联
下面是HotSpot JVM中生成hash散列值的实现:
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it's possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia's xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
该实现位于hotspot/src/share/vm/runtime/synchronizer.cpp文件下。
2:hashCode 的作用?
hashCode 的作用在于 效率 二字,如上个案例,当我们放入第二个重复对象的时候。他先会去比较hashCode 值,如果不一致 ,直接说明两个内容不一致。放入容器内。如果 hashCode 一致,再去执行equals 方法 进行 一个一个 value 的对比。如果此时容器内有 1000 个 ,这时在添加1个元素 。 没有hashCode 那不是要对比 1000次 ,而且每次都一个一个 value 的对比。那效率可想而知!!!
案例1:我们可以看看 String hashCode()的源码实现。 可以看出hashCode 的结果是固定的,并不是随机的。只要 值一致就会产生一样的结果。所以 以此说明如果 hashCode 都不一致 ,那肯定内容不一致
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
案例2:String equals()的源码实现,可以看出先比较引用,然后就是完全的比较 每个字符了。这样保证内容都是一致的
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3:两个对象 hashCode 一致,他们完全相同吗?
不一定,以 String 来说, hashCode 一致那么内容肯定是一致了。 equals 方法肯定也是 true 。但是他们附属在的对象内存不一致
假例:第一个 hashCode =1018547642 内存地址=@1018547642 那第二个 hashCode 也是一样。内存地址也一样,但是该地址已经存放数据了, 就需要再次找到个没人占用的内存地址进行存储 。 不相同指出就在于 内存地址
3:为什么重写了 equals() ,还要重写 hashCode()
(1):自定义对象没有重写 hashCode ,那么放入 Set 集合类型的容器就会导致重复的元素放入到容器内。
因为 Set 类型集合的执行顺序:
会先判断 hashCode 如果连 hashCode 都不一致
那就不会执行 equals 方法了
这也牵扯出另一个问题 , 就是效率 。 如果没有重写 hashCode ,每次都去 执行 equals 方法和容器内的所有元素统统对比一次。那程序的效率将导致非常低下!!!
如有错误,恳请指出!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步