原理
重写equals方法时一定要重写hashCode方法的原因是为了保证对象在使用散列集合(如HashMap、HashSet等)时能够正确地进行存储和查找。
案例
多说无益,直接开始实践
测试方法
void testStudent() {
Map<Student, String> studentMap = new HashMap<>();
Student student1 = new Student("Alice", 1);
Student student2 = new Student("Alice", 1);
studentMap.put(student1, "A");
System.out.println("hashcode是否相等 "+ (student1.hashCode() == student2.hashCode()));
System.out.println("对象是否相等 "+ (student1.equals(student2)));
System.out.println(studentMap.get(student2));
}
测试一
Student类(未重写equals和hashcode)
public class Student {
private String name;
private int id;
public Student(String name, int id) {
this.name = name;
this.id = id;
}
}
可以看到,当我们两个方法都未进行重写的时候,对象和hashCode都不一致,所以当我们存进散列表的时候,会一直存放进我们认为相同的对象,也就是并不唯一。
测试二
Student类(重写equals,未重写hashcode)
public class Student {
private String name;
private int id;
public Student(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Student student = (Student) obj;
return id == student.id && Objects.equals(name, student.name);
}
}
为什么对象相等,但是还是找不到该student对象呢?因为散列表找的时候,首先是调用hashcode去进行寻找是否有重复的对象,如果不重复就表示没有找到,如果有该hashcode,就需要再调用一次equals判断是否真正相等(哈希冲突)。你可以试一下,只重写hashcode,但是不重写equals,最后还是取不到该对象。
带你深入一下源码,看一下过程。以下只截取主要核心的代码
//测试方法中的查找方法get,鼠标点进去
studentMap.get(student2)
public V get(Object key) {
Node<K,V> e;
//首先调用了hash,获取hash值,再调用getnode,这里点进去hash方法
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
static final int hash(Object key) {
int h;
//可以看到,这里最后调用了hashcode的方法,而hashcode是native方法,下面放JavaGuide对
//hashcode的解释
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public native int hashCode();
//所以首先先获取hash值,如果在散列表找到,才需要去调用equals,我们进入getNode方法进去看看
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
//看一下这里,如果散列表中不为空,表示可能有对应的值
(first = tab[(n - 1) & hash]) != null) {
//然后再调用一次equals判断对象是否相等
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
测试三
Student类(重写equals和hashcode)
public class Student {
private String name;
private int id;
public Student(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Student student = (Student) obj;
return id == student.id && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, id);
}
}