【翻译】玩转Java的
hashCode()
与
equals()
方法
hashCode()
与
equals()
方法
本文将讨论Java中的
hashCode()
和
eqauls()
方法的默认实现,如何正确地重写,以及如何使用
Apache Commons
包的一些工具类。
hashCode()
和
eqauls()
方法在
Object
类中定义的,因此,所有的Java对象继承了这两个方法的默认实现。
一、hashCode()
和eqauls()
方法的使用
hashCode()
方法用来获取给定对象的特定整型值(哈希码)。在像HashMap
类似的数据结果中,这个整数用来确定对象存储在哪个哈希桶(Hash bucket)中。默认情况下,对象的hashCode()
方法返回的这个整型值代表了对象存储的内存地址。
equals()
方法用来判断两个对象是否相等。默认的实现仅简单地检查两个对象的引用地址是否相等。
二、重写默认的行为
如果我们在自定义的类中不重写任何方法,可以确保所有的方法运行正常。但是,在应用软件中经常需要去改变一些对象的默认行为。让我们来看一个包括Employee
类的代码实例。首先,让我们来创建一个Employee
类:
public class Employee {
private Integer id;
private String firstname;
private String lastname;
private String department;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
上面的Employee
类封闭了一些基本的属性及其访问方法。现在,我们来考虑一个简单的场景,即,我们需要判断两个Employee
对象是否相等。
public class EqualsTest {
public static void main(String[] args) {
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
System.out.println(e1.equals(e2));
}
}
上述代码将返回“false”。但是从实际情况来看,这两个对象代表的其实是同一个employee,因此,在我们的应用中应该返回true。
为了实现上述的行为,我们需要按如下的方式重写equals()
方法:
@Override
public boolean equals(Object o) {
if(o == null){
return false;
}
if(o == this){
return true;
}
if(o instanceof Employee){
Employee e = (Employee) o;
return (this.getId() == e.getId());
}
return false;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "Employee:[id: "+this.getId() + "]";
}
将上述方法添加到Employee
类后,EqualsTest
将返回true。
到此,我们就完成了所有工作了吗?当然不是。让我们用不同的方法来对测试一下修改后的Employee
类。
public class EqualsTest {
public static void main(String[] args) {
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints 'true'
System.out.println(e1.equals(e2));
Set<Employee> employees = new HashSet<>();
employees.add(e1);
employees.add(e2);
//Prints two objects.
System.out.println(employees);
}
}
输出结果:
true
[Employee:[id: 100], Employee:[id: 100]]
上述代码打印HashSet
对象employees
时打印了两个。如果两个employees
对象相等,那么Set
中应该只保存一个对象。什么地方出错了呢?
我们还需要重写另一个重要的方法hashCode()
。如Java Docs中所描述的,如果我们重写equals()
方法,那么我们必须同时重写hashCode()
方法。因此,让我们在Employee
类中添加另一个方法。
@Override
public int hashCode() {
// TODO Auto-generated method stub
final int PRIME = 31;
int result = 1;
result = PRIME * result + getId().hashCode();
return result;
}
执行EqualsTest
,输出结果:
true
[Employee:[id: 100]]
当我们在Employee
类中添加了上述方法,EqualsTest````HashSet
对象employees
时仅打印了一个对象,从而证明e1与e2相等性。
三、使用Apache Commons Lang
重写hashCode()
和equals()
方法
Apache Commons
提供了两个非常优化的工具类HashCodeBuilder
和EqualsBuilder
,用来生成hashCode()
和equals()
方法。下面是其使用方法:
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
public class Employee {
private Integer id;
private String firstname;
private String lastname;
private String department;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
@Override
public boolean equals(Object o) {
if(o == null){
return false;
}
if(o == this){
return true;
}
if(o instanceof Employee){
Employee e = (Employee) o;
return new EqualsBuilder().append(getId(), e.getId()).isEquals();
}
return false;
}
@Override
public int hashCode() {
final int PRIME = 31;
return new HashCodeBuilder(getId() % 2 == 0 ? getId() + 1 : getId(), PRIME).toHashCode();
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "Employee:[id: "+this.getId() + "]";
}
}
此外,如果我们使用集成开发环境(IDE),我们也可以利用IDE来自动生成一个结构良好的代码。例如:在Eclipse IDE中可以右击**class>>source>>Generate hashCode() and equals()…,就可以为我们生成实现良好的代码。
四、需要记住的关键点
- 通常需要使用同一个属性来生成
hashCode()
和equals()
方法。在上面的示例中,我们使用的是Employee id
。 - 需要保证
equals()
方法返回值的一致性。即,如果对象没有被修改,那么,多次调用equals()
方法比较时返回同样的值。 - 当
a.equals(b)
返回true
时,a.hashCode()
与b.hashCode()
必须相等。 - 如果我们重写了
equals()
和hashCode()
两个方法中的任意一个,则必须重写另一个。
五、在ORM
中使用的特殊点
**Info: **ORM,Object Relation Mapping,对象关系映射,用于实现面向对象编程语言里不同类型系统的数据之间的转换。
我们在处理对象关系映射时,在hashCode()
和equals()
方法中需要确保访问对象属性时使用getters
,而不是使用属性引用。因为,在对象关系映射的处理中,有时会使用迟延加载来实例化属性,对象属性直到调用getter
方法时才可用。