首先我们看下Object类的equals方法和hashCode方法:hashCode()方法是本地方法,返回的是对象引用中存储的对象的内存地址;equals()方法是利用==来比较两个对象的内存地址是否相同。
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
在实际应用中,我们常常需要重写equals方法,以满足需求:如果两个对象指向的内存地址相同或者两个对象各个字段属性值相同,则认为两个对象相等。
那么为什么重写equals的同时也需要对hashCode()方法进行重写呢?
首先我们看下equals和hashCode之间的关系:如果equals方法返回true,则两个对象的hashCode值必须相等;如果equals方法返回false,则两个对象的hashCode值可以相等,也可以不相等。但在实际开发中,我们往往不希望出现这种equals返回false,但两者的hashCode值相等的情况,因为这样会带来hash冲突的问题。
我们来看看示例代码:
第一种:没有重写equals和hashCode方法
public class Student {
private String name;
private int age;
public Student(String name,int age) {
super();
this.name = name;
this.age=age;
}
}
public static void main(String[] args){
Student student1=new Student("yue",21);
System.out.println("student1的hashCode:"+student1.hashCode());
Student student2=new Student("yue",21);
System.out.println("student2的hashCode:"+student2.hashCode());
System.out.println("student1.equals(student2)?"+student1.equals(student2));
}
以上情况将会输出两个对象不相等的结果,这个很容易理解,没有重写equals方法,那么就相当于是使用的Object的equals方法,而这里的student1和student2在堆内存中的地址是不同的,因此返回的结果一定是false。
student1的hashCode:2018699554
student2的hashCode:1311053135
student1.equals(student2)?false
第二种:只重写equals方法而没有重写hashCode方法
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
在这里我们添加一个HashMap来存储数据:
public static void main(String[] args){
Student student1=new Student("yue",21);
System.out.println("student1的hashCode:"+student1.hashCode());
Student student2=new Student("yue",21);
System.out.println("student2的hashCode:"+student2.hashCode());
System.out.println("student1.equals(student2)?"+student1.equals(student2));
Map<Student,String> map=new HashMap<>();
map.put(student1, "student1");
map.put(student2, "student2");
System.out.println(map.get(student1));
System.out.println(map.get(student2));
}
我们先猜测下这段代码会输出什么?
首先,由于我们重写了equals方法,且student1和student2的字段属性值也完全相同,因此student1.equals(student2)?结果是true。而hashCode()方法仍然比较的是内存地址的值,所以输出结果两者不相等。
那么对于hashMap的输出结果呢?
我们知道HashMap集合判断两个元素相等的标准是:两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值也相等。
* 如果两个元素通过equals()方法比较返回true,且它们的hashCode()方法返回值相同,则HashMap会找到这个位置,使用新值替换掉旧值。
* 如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不同,HashMap会把它们存储在不同的位置,依然可以添加成功。
基于以上,理想状态下我们希望程序输出的结果是map.get(student1)和map.get(student2)的值是相等的,也就是student2覆盖掉原来的值,即输出的都为student2.但是由于以上代码没有重写hashCode值,虽然equals的值是相等的,但是对于HashMap中根据hash值查找key来说并没有作用,因此hashMap认为student1和student2是两个不同的key,因此存储的位置不同,get返回的结果也不同。
输出结果:
student1的hashCode:2018699554
student2的hashCode:1311053135
student1.equals(student2)?true
student1
student2
第三种:重写equals方法和hashCode方法:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
使用第二种情况中的示例代码,输出结果是我们的预期结果:
student1的hashCode:121621
student2的hashCode:121621
student1.equals(student2)?true
student2
student2
总结:在重写equals()方法的同时也要重写hashCode()方法。
如果只重写equals()方法而不重写hashCode()方法,将会违背equals方法的特性原则:equals与hashCode的定义必须一致,两个对象equals为true,就必须有相同的hashCode。反之则不成立。对于HashMap、HashSet等集合,如果不重写hashCode()方法,将会使得字段属性值完全相同的两个对象由于hashCode不同,在HashMap或HashSet中的table数组的下标不同,从而使得整个对象存储在集合中的不同位置。