目录
一、hashCode的概念
二、为什么要有hashCode
三、为什么重写equals要重写hashCode
四、HashSet源码分析
五、容易记混的点
一、hashCode的概念
hashCode()是Object定义的方法,它将返回一个整型值,这个方法通常用来将对象的内存地址转换为整数之后返回,它存在的价值是为Hash容器处理数据时提供支持,Hash容器可以根据hashCode定位需要使用的对象,也可以根据hashCode来排除2个不相同的对象,即:hashCode不同,则视为2个对象不同。
二、为什么要有hashCode
哈希码主要在哈希表这类集合映射的时候用到,哈希表存储的是键值对(key-value),它的特点是:能根据“键”快速的映射到对应的“值”。这其中就利⽤到了哈希码。
例如 HashMap 怎么把 key 映射到对应的 value 上呢?用的就是哈希取余法,也就是拿哈希码和存储元素的数组的长度取余,获取 key 对应的 value 所在的下标位置。
三、为什么重写equals要重写hashCode
这里我们举例说明:
1、新建一个实体类QueryBO。
import lombok.Data;
/**
* 查询
*
* @author yunyan
* @date 2023/7/8
*/
@Data
public class QueryBO {
private Boolean query1;
private Boolean query2;
private Boolean query3;
}
测试类:
/**
* 测试hashCode
*
* @author yunyan
* @date 2023/7/18
*/
public class TestHash {
public static void main(String[] args) {
QueryBO queryBO1=new QueryBO();
queryBO1.setQuery1(true);
queryBO1.setQuery2(true);
queryBO1.setQuery3(false);
QueryBO queryBO2=new QueryBO();
queryBO2.setQuery1(false);
queryBO2.setQuery2(false);
queryBO2.setQuery3(false);
System.out.println(queryBO1.equals(queryBO2));
}
}
如果是原有的equals和hashCode方法,可以看出,如果没有重写equals方法,那么两个对象进行equals比较时,只有当两个对象的所有属性都相等时,才会认为两个对象相等。
结果:
2、在QueryBO类里只重写equals方法
import lombok.Data;
import java.util.Objects;
/**
* 查询
*
* @author yunyan
* @date 2023/7/8
*/
@Data
public class QueryBO {
private Boolean query1;
private Boolean query2;
private Boolean query3;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
QueryBO queryBO = (QueryBO) o;
return Objects.equals(query3, queryBO.query3);
}
}
结果:现在只要两个对象的query3属性相同,那么就认为两个对象相同
3、继续测试,现在我使用HashSet集合将两个对象添加进去
import java.util.HashSet;
import java.util.Set;
/**
* 测试hashCode
*
* @author yunyan
* @date 2023/7/18
*/
public class TestHash {
public static void main(String[] args) {
QueryBO queryBO1=new QueryBO();
queryBO1.setQuery1(true);
queryBO1.setQuery2(true);
queryBO1.setQuery3(false);
QueryBO queryBO2=new QueryBO();
queryBO2.setQuery1(false);
queryBO2.setQuery2(false);
queryBO2.setQuery3(false);
System.out.println(queryBO1.equals(queryBO2));
Set<QueryBO> set=new HashSet<>();
set.add(queryBO1);
set.add(queryBO2);
System.out.println(set);
}
}
结果是:
这里我们都知道set集合是去重的,明明我们的两个对象都相等,但是set集合却认为是两个不同的对象。
原因是: HashSet、HashMap集合在添加元素的时候,用哈希取余法,也就是拿hashCode和存储元素的数组的长度取余,获取 key 对应的 value 所在的下标位置。如果获取到的下标位置上已经存在元素,则认为产生了哈希碰撞(hashCode() 所使用的散列算法也许刚好会让多个对象传回相同的散列值。)再拿新添元素(新的)与当前位置上的元素(旧的)进行equals比较,如果返回true就用新的替换旧的。如果返回false就添加到该位置下面的链表尾或红黑树上。而如果没有发生哈希碰撞,也就间接说明两个对象不相等。然而我们上面只是重写了equals方法(只比较query3是否相同),而hashCode返回的还是三个query属性组合成的数组的hashCode,所以虽然equals返回true,但是两个对象的hashCode不一样,set里存在两个QueryBO对象而不是一个。
现在在实体类里重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(query3);
}
看一下程序的运行结果:
这就是为什么重写equals方法必须要重写hashCode方法的原因。
四、HashSet源码分析
下面我们查看HashSet的源码来分析情况
1、首先我们调用了HashSet的构造方法
他的内容是创建一个新的HashMap
2、 原来HashSet是借助HashMap实现的,我们来看看他的add方法,通过idea中的Structure可以看到类的结构。
3、到这里我们可以发现,这个add方法,调用的也是map的put方法。以传入的元素作为Key,PRESENT作为Value,而PRESENT其实是一个空对象。
4、下面来看看put方法
从这里可以看出来,HashMap对于节点的判断其实是利用了hash函数,而这个hash函数是借助了传入的Key的Hash函数
所以说,如果我们只重写了equals没有重写hashcode,就会发生与期望结果不相符的情况。
五、容易记混的点
- hashCode相等,两个字符串不一定相等。
- hashCode相等,这两个对象不一定相等。
- hashCode不等,这两个对象一定不相等。
- 如果两个对象分别调⽤ equals 方法都返回 true,则这两个对象一定相等。