默认情况下,java.lang.object提供了两个重要方法来进行对象之间的比较:equals()
和hashcode()
。这两个方法在大型项目中多个类之间的交互中非常有用。在这篇文章中,主要介绍这里两个方法之间的联系、默认实现、以及开发者重写这两个方法的场景。
方法定义以及默认实现
equals(Object obj)
java.lang.Object中定义的用来表明一个其他对象是否等同于(equal to)当前对象。jdk中Object中的默认实现是基于两个对象的内存位置——即当且仅当两个对象所占内存地址相同,这两个对象才等同。hashcode()
这个方法会返回一个随机整型数值,在处理哈希表(hash table)时大有裨益(详见后续的HashMap讲解)。
equals()和hashcode()之间联系
Object中两个方法的默认实现常常不能满足业务需求,经常需要根据业务需求来确定两个对象是否相等。
根据Java文档,这两个方法均需要被重写才能保证万无一失——也就是说,单纯重写equals()
方法是不够的
If two objects are equal according to the equals(Object) method, then calling the hashcode() method on each of the two objects must produce the same integer result.
译文:如果两个对象等同(equals()方法返回值为true),那么这两个对象调用hashcode()方法时,得到的整型数值必须相等。
下面的实例用来展示同时重写两个方法的必要性以及只重写equals()
方法的弊端。
实例
定义一个学生类:
package org.my.beans;
public class Student {
private int id;
private String name;
public Student(int id, String name) {
this.name = name;
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
同时定义一个HashcodeEquals 类,用于验证两个Student 实例(具有相同属性)是否等同。代码如下:
public class HashcodeEquals {
public static void main(String[] args) {
Student alex1 = new Student(1, "Alex");
Student alex2 = new Student(1, "Alex");
System.out.println("alex1 hashcode = " + alex1.hashCode());
System.out.println("alex2 hashcode = " + alex2.hashCode());
System.out.println("Checking equality between alex1 and alex2 = " + alex1.equals(alex2));
}
}
输出:
alex1 hashcode = 3459493747
alex2 hashcode = 7423689063
Checking equality between alex1 and alex2 = false
结论:
即使两个对象具有相同的属性值,当时它们存储在不同的内存地址中。因此它们在默认equals()
实现下被认为是不等同的。hashcode()
也同理——生成的两个整型数值并不相等。
重写equals()方法
假如在业务场景中,我们认为两个学生对象如果具有相同id
,那么我们认为这两个学生对象是等同的。所以我们重写equals()
方法以实现上述业务需求,重写代码如下:
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof Student))
return false;
if (obj == this)
return true;
return this.getId() == ((Student) obj).getId();
}
通过以上代码可以看出,当且仅当具有相同内存地址或者相同id
两个Student对象才认定为等同。重写equals()
后,继续执行一遍上面介绍的HashcodeEquals,得到输出如下:
alex1 hashcode = 2032578917
alex2 hashcode = 1531485190
Checking equality between alex1 and alex2 = true
重写hashcode()
通过在上面重写equals()
方法,我们完成了业务需求——即使两个相同id
的学生对象具有不同的hashcode值。那么重写hashcode()
的目的是什么呢?
我们通过一个HashSet中存放两个相同id
的学生来进行验证。
public class HashcodeEquals {
public static void main(String[] args) {
Student alex1 = new Student(1, "Alex");
Student alex2 = new Student(1, "Alex");
HashSet < Student > students = new HashSet < Student > ();
students.add(alex1);
students.add(alex2);
System.out.println("HashSet size = " + students.size());
System.out.println("HashSet contains Alex = " + students.contains(new Student(1, "Alex")));
}
}
我们得到结果如下:
HashSet size = 2
HashSet contains Alex = false
AHA!我们已经重写equals()并且已经验证alex1、alex2是等同的,我们都知道HashSet只存储不同对象,那么为什么它认为他们是不同的对象?
HashSet将在内存中将对象在不同桶(bucket)进行存储。每个桶都是链接到一个特定的hashcode。由于alex1以及alex2有不同的hashcode,它们将存储在不同的桶中,因此,将alex2认为是一个与alex1完全不同的对象。
现在再重写hashcode()
,使得学生对象的hashcode等于其id
,使得id
相同的学生存储在同一个桶:
@Override
public int hashCode() {
return id;
}
得到输出结果如下:
HashSet size = 1
HashSet contains Alex = true
看吧! 现在两个学生对象才是真正意义上的等同,这就是重写hashcode()
的重要性。
HashMap,HashTable等利用hash机制的结构也均有类似的性质。
结论
在工作用业务需要需要重写equals()以及hashcode()时,需要遵从以下原则:
1、如果两个对象equals()方法返回值为true,那么一定一定一定要具有相同的hashcode
2、如果两个对象具有相同的hashcode,那么这两个对象未必等同。(equals返回未必为true)。
3、仅重写equals()方法会使得HashSet,HashMap,HashTable等用到hash机制的结构出现问题。
希望对大家有所帮助。