重写equals() 和 hashCode()方法

为什么要重写equlas方法

比较两个java对象是否相等时, 我们习惯调用对象的equlas方法进行判断。 先看如下代码:

有一个学生类Student,此处省略了gettersetterconstructor方法。

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。但是在实际生活中,s1s3是分属不同体系的两个学生。

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)返回结果是trues1.equals(s3)返回结果是false

符合实际生活情形,s1s2是同一个学生,s1s3是分属不同体系的两个学生。

所以推荐使用getClass()方法的方式重写equals方法。

为什么要重写hashCode方法

由于hashCode方法是Object类中的,所以每一个对象都有一个默认的散列码,其值为对象的存储地址。

EqualshashCode的定义必须一致:如果x.equals(y)返回true, 那么x.hashCode()就必须与y.hashCode()具有相同的值。

java中规定,重写类的equlas方法时,必须重写类的hashCode方法。以便将对象插入散列表中。

如何重写hashCode方法

原则:哪些属性在equals方法中参与比较,在hashCode方法中就要散列哪些属性。

在java中主要有以下3种方法来重写hashCode()方法。

  1. 使用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值。

  2. jdk1.7之后可以直接用以下方法重写hashCode方法,本案例为:
    public int hashCode() {
         return Objects.hash(id,name,age);
    }
    

    使用Objects类提供的hash方法重写,有多少个参数参与equals比较,则在hash方法的参数中填写多少个参数。推荐使用此种方式,因为简单啊

  3. 使用Apache Commons Lang包的的EqualsBuilderHashCodeBuilder方法。

    使用这个中提供的方法,需要先用其提供的方法重写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();
     }
    
原文链接: https://marshucheng1.github.io/2017/02/05/java-oop/