重写equals() 和 hashCode()方法
为什么要重写equlas方法
比较两个java
对象是否相等时, 我们习惯调用对象的equlas
方法进行判断。
先看如下代码:
有一个学生类Student
,此处省略了getter
、setter
、constructor
方法。
public class Student {
/*学号*/
private int id;
/*姓名*/
private String name;
/*年龄*/
private int age;
/*getter...*/
/*setter...*/
/*constructor...*/
}
有一个测试类TestStudent
,在测试类中有如下代码:
Student s1=new Student(1001,"张三",22);
Student s2=new Student(1001,"张三",22);
System.out.println(s1.equals(s2));
如果不重写equlas
方法,默认调用的都是继承自Object
类的equlas
方法。
该方法的具体内容如下:
public boolean equals(Object obj) {
return (this == obj);
}
该方法仅仅只是判断对象的引用地址值是否相等。所以默认的equlas
方法返回false
。
而在实际需求中,学号、姓名、年龄都相同时,很明显是同一个人。所以我们只需要保存一条数据。
我们期望的是equlas
方法返回结果true
,这样在后续代码中,假如需要存储数据到数据库。
我们只需要存1个Student
对象即可。基于上述原因,我们需要重写equals
方法。
equals方法的重写
重写equals
方法,当学号、姓名、年龄都相同时,让equals
方法返回结果为true
。
在这里有2种重写equals
方法的写法。
1.instanceof
关键词方式
重写的equals
方法如下:
public boolean equals(Object obj) {
if(this==obj) {
return true;
}
if(obj==null) {
return false;
}
if(obj instanceof Student) {
Student stu=(Student)obj;
if(stu.getId()==this.getId() &&
stu.getName().equals(this.getName()) &&
stu.getAge()==this.getAge()) {
return true;
}
return false;
}
return false;
}
按这种方式重写equlas
方法后。如下代码返回结果为true
说明是s1和s2是同一个人(尽管在内存中是2块区域,但实际生活中,是同一个人)。
Student s1=new Student(1001,"张三",22);
Student s2=new Student(1001,"张三",22);
System.out.println(s1.equals(s2));
使用instanceof
关键词重写的equals
方法,在子类继承父类时。
用子类对象与父类对象比较相等时,会出现一些不符合实际的情况发生。
如现有一个中学生类MiddleStudent
继承我们的学生类Student
。代码如下:
public class MiddleStudent extends Student{
/*学号*/
private int id;
/*姓名*/
private String name;
/*年龄*/
private int age;
/*getter...*/
/*setter...*/
/*constructor...*/
}
在测试类TestStudent
中,有如下代码:
Student s1=new Student(1001,"张三",22);
Student s2=new Student(1001,"张三",22);
MiddleStudent s3=new MiddleStudent(1001,"张三",22);
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
此时s1.equals(s3)
返回结果也是true
。但是在实际生活中,s1
与s3
是分属不同体系的两个学生。
s1
是属于大学生体系的学生。s3
是属于中学生体系的学生。尽管他们的学号、姓名、年龄都相同。
但是实际上他们不是同一个人。所以instanceof
关键词实现的equals
方法略有问题。
2.getClass()
方法方式
重写的equals
方法如下:
public boolean equals(Object obj) {
if(this==obj) {
return true;
}
if(obj==null) {
return false;
}
if(obj.getClass() == this.getClass()) {
Student stu=(Student)obj;
if(stu.getId()==this.getId() &&
stu.getName().equals(this.getName()) &&
stu.getAge()==this.getAge()) {
return true;
}
return false;
}
return false;
}
在测试类TestStudent
中,有如下代码:
Student s1=new Student(1001,"张三",22);
Student s2=new Student(1001,"张三",22);
MiddleStudent s3=new MiddleStudent(1001,"张三",22);
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
此时,s1.equals(s2)
返回结果是true
,s1.equals(s3)
返回结果是false
。
符合实际生活情形,s1
与s2
是同一个学生,s1
与s3
是分属不同体系的两个学生。
所以推荐使用getClass()
方法的方式重写equals
方法。
为什么要重写hashCode方法
由于hashCode
方法是Object
类中的,所以每一个对象都有一个默认的散列码,其值为对象的存储地址。
Equals
与hashCode
的定义必须一致:如果x.equals(y)
返回true
,
那么x.hashCode()
就必须与y.hashCode()
具有相同的值。
java
中规定,重写类的equlas
方法时,必须重写类的hashCode
方法。以便将对象插入散列表中。
如何重写hashCode方法
原则:哪些属性在equals
方法中参与比较,在hashCode
方法中就要散列哪些属性。
在java中主要有以下3种方法来重写hashCode()
方法。
- 使用17和31散列码的方式来重写,本案例为:
public int hashCode() { int result=17; result=31*result+Integer.hashCode(id); result=31*result+name.hashCode(); result=31*result+Integer.hashCode(age); return result; }
当有多个其他参数时,继续仿照这种格式写。其中,如果是基本类型的值。
可以用其包装类来生成
hashCode
值。 jdk1.7
之后可以直接用以下方法重写hashCode
方法,本案例为:public int hashCode() { return Objects.hash(id,name,age); }
使用
Objects
类提供的hash
方法重写,有多少个参数参与equals
比较,则在hash
方法的参数中填写多少个参数。推荐使用此种方式,因为简单啊
!-
使用
Apache Commons Lang
包的的EqualsBuilder
和HashCodeBuilder
方法。使用这个中提供的方法,需要先用其提供的方法重写
equals
方法,然后再用其提供的方法重写
hashCode
方法,本案例中使用包为:commons-lang-2.6
,具体代码如下。使用
EqualsBuilder
类提供的方法重写equals
方法。public boolean equals(Object obj) { if(this==obj) { return true; } if(obj==null) { return false; } if(obj.getClass() == this.getClass()) { Student stu=(Student)obj; return new EqualsBuilder() .append(id, stu.getId()) .append(name, stu.getName()) .append(age, stu.getAge()) .isEquals(); } return false; }
使用
HashCodeBuilder
类提供的方法重写hashCode
方法public int hashCode() { return new HashCodeBuilder(17, 37) .append(id) .append(name) .append(age) .toHashCode(); }