为什么hashCode和equals方法要一起重写
参考连接:https://zhuanlan.zhihu.com/p/50206657
一、问题
问题:HashSet不允许存放重复的对象,但在重写equals方法但没有重写hashCode方法的前提下,两个对象相等,哈希值不相等
代码:
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
public class HashSet_ {
public static void main(String[] args) {
HashSet<Object> hashSet = new HashSet<>();
Employee em1 = new Employee("张豪", 24);
Employee em2 = new Employee("李四", 21);
Employee em3 = new Employee("张豪", 24);
hashSet.add(em1);
hashSet.add(em2);
hashSet.add(em3);
System.out.println(em1.equals(em3));
System.out.println(em1.hashCode());
System.out.println(em3.hashCode());
}
}
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果
进入hashCode方法,进入到Object类
//true
//356573597
//21685669
二、原因
1、hashCode的约定
当我们将equals方法重写后有必要将hashCode方法也重写,这样才能保证不违背hashCode方法中“相同对象必须有相同的哈希值”的约定
(1)什么是hashCode()方法
hashCode()方法的本质就是一个哈希函数,将对象的地址值映射未Integer类型的哈希值
要求:
- 一个对象多次调用它的hashCode()方法,应该返回相同的哈希值(Integer)
- 两个对象如果通过equals方法判定为相等,那么就应该返回相同的哈希值(Integer)
- 两个地址值不相等的对象调用hashCode方法不要求返回不相等的哈希值,即对象不相等哈希值可以相等
- 哈希值不等,必是不同的对象
(2)结论
equals方法和hashCode方法配套使用,对于任何一个对象,hashCode方法必须要完成一件事:为该equals方法认定相同发对象返回相同的哈希值,即当我们根据业务改写了equals方法时,也应当同时改写hashCode方法的实现,否则hashCode方法依然返回的是依据Object类中的依据地址得到的哈希值(Integer)
2、以String类为例,了解同时重写的目的
(1)equals方法
重写父类的equals方法,如果两个对象的地址一样或者内容一样(即地址不同,为两个对象),则认为两者相等
public boolean equals(Object anObject) {
if (this == anObject) {//两个对象地址相同
return true;//返回true
}
if (anObject instanceof String) {//如果是String型对象
String anotherString = (String)anObject;//强制向下转换
int n = value.length;//value为charp[]数组存放字符串
if (n == anotherString.value.length) {//如果两个数组长度相等
char v1[] = value;//分别记录两个数组
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {//遍历,循环n次
if (v1[i] != v2[i])//如果有一个字符不相等
return false;//则两个数组不相等
i++;
}
return true;//如果没有返回false,说明每个字符都相等,返回true
}
}
return false;
}
如果未重写hashCode()方法,会调用父类hashCode方法来计算哈希值,即以两个字符串对象各自的地址映射为哈希值,即被equals认定相等的两个不同对象,拥有不同的哈希值,与两个对象如果通过equals方法判定为相等,那么就应该返回相同的哈希值(Integer)的约定不相符,因此我们需要重写hashCode方法,以得到这样一个结果:必须保证重写后的equals方法认定相同的两个对象拥有相同的哈希值。
(2)hashCode方法
public int hashCode() {
int h = hash;//h记录hash值
if (h == 0 && value.length > 0) {//如果哈希值为0且数组长度大于0
char val[] = value;
for (int i = 0; i < value.length; i++) {//遍历数组
h = 31 * h + val[i];//新的哈希函数返回新的哈希值
}
hash = h;
}
return h;//返回新的哈希值,哈希值由数组元素决定,数组元素相同,哈希值相同
}
此时,便保证了,重写后的equals方法认定相同的两个对象拥有相同的哈希值
由此,得到一个结论:hashCode方法的重写原则就是保证equals方法认定为相同的两个对象拥有相同的哈希值
3、为什么要保证相同对象有相同的哈希值
在HashMap中,通过key对应的哈希值找到对应的地址来存放元素,如果没有重写hashCode方法,即两个相同的key有着不同的哈希值,那么,将不能再通过key找到对应的索引,散列表的数据结构将不存在。
因此,我们可以认为hashCode方法不仅仅是与equals配套使用的,它甚至是与Java集合配套使用的。
三、解决办法
在Employee类中重写equals方法和hashCode方法
class Employee {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}