重写hashCode()与equals()的必要性
只有在使用HashMap、HashSet等数据结构时,并且存储的对象是我们自定义的对象时,才需要重写。
基本类型的包装类以及String已经有了默认实现。
例如Integer类
public static int hashCode(int value) {
return value;
}
以及String类
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;
}
上述内置的类在使用过程中都不需要做处理,下面再看看我们自定义的类。
定义一个类Student
class Student{
/**姓名**/
private String name;
/**性别**/
private Integer gender;
/**密码**/
private String pwd;
public Student(String name, Integer gender, String pwd) {
this.name = name;
this.gender = gender;
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
new三个相同属性的对象,获取hashCode
public class Test {
public static void main(String[] args) {
Student student1 = new Student("a", 1, "pwd");
Student student2 = new Student("a", 1, "pwd");
Student student3 = new Student("a", 1, "pwd");
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
System.out.println(student3.hashCode());
HashSet<Student>set = new HashSet<>();
set.add(student1);
System.out.println(set.contains(student2));
}
}
hasCode各不相同
460141958
1163157884
1956725890
false
假如在现有场景下,name是唯一主键,只要name相同,就默认Student相同。
所以在Student类中重写hashCode()
@Override
public int hashCode() {
return Objects.hash(name);
}
得到结果
128
128
128
false
hashCode相同了,但是得到的结果还是false。这是因为没有重写equals()方法,即使三个对象的hashCode()相同,也会被当成hash冲突来处理,这三个对象依然是不等价的。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return name.equals(student.name);
}
重写equals()后,这三个对象才算是等价的。
128
128
128
true
自定义对象默认的hashCode
自定义对象hashCode是根据对象的地址计算得出的。
去掉Student中重写的hashCode(),重新测试下
public class Test {
public static void main(String[] args) {
Student student1 = new Student("a", 1, "pwd");
Student student2 = new Student("a", 1, "pwd");
Student student3 = new Student("a", 1, "pwd");
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
System.out.println(student3.hashCode());
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
}
}
得到的结果:
460141958
1163157884
1956725890
com.dayrain.demo12.Student@1b6d3586
com.dayrain.demo12.Student@4554617c
com.dayrain.demo12.Student@74a14482
现在删除中间的student2
public class Test {
public static void main(String[] args) {
Student student1 = new Student("a", 1, "pwd");
Student student3 = new Student("a", 1, "pwd");
System.out.println(student1.hashCode());
System.out.println(student3.hashCode());
System.out.println(student1);
System.out.println(student3);
}
}
得到的结果:
460141958
1163157884
com.dayrain.demo12.Student@1b6d3586
com.dayrain.demo12.Student@4554617c
Java按照顺序给对象分配内存,与我们所定义的对象名称无关。
并且hashCode是通过对象的地址计算的,而非对象名。
(上述的实验并不能得出相关结论,经过后续的资料查找,发现hashCode有多种实现方式,可以通过-XX:hashCode = 指定)
默认情况下是-XX:hashCode = 5,是一个与当前线程有关的随机数与其他三个固定值进行xorshift运算后的结果数。
具体可参考
JDK核心JAVA源码解析(9) - hashcode 方法_张哈希的博客-CSDN博客