这篇文章算是一个翻译,原文在:点击打开链接,但我并没有一字不差全部翻译,只是选出一些重点,大家有兴趣可以看看原文。
Equals和 hashCode是java中一个对象的两个基本方法和core java的重要组成部分。Equals用来比较对象的相等性,hashcode用来生成相应对象的整数形编码。Equals 和 hashCode在core java中有广泛的应用,例如在hashmap中插入和取回数据。
Equals方法覆写遵循以下规则:
(1)自反性。一个对象必须和它自身相等;
(2) 对称性。如果a.equals(b) 为true 那么 b.equals(a) 也必须为true;
(3) 传递性。如果 a.equals(b) 为true 并且 b.equals(c) 为 true 那么 c.equals(a) 也必须 为 true;
(4) 一致性。除非字段的值改变,否则多次调用equals()方法应该返回相同的结果。
(5) Null比较。任何对象和null比较必须返回false,并且不能够出现NullPointerException结果。
Equals 和 hashCode的关系
Equals()和hashcode()必须遵守以下规则:
(1) 如果两个对象执行equals()方法是相等的,那么执行hashcode()方法的结果也必须是相等的;
(2) 如果两个对象执行equals()方法不相等,那么执行hashcode()方法的结果可以相等页可以不相等。
重写equals方法的步骤
这是大多数java程序员重写equals方法的标准做法:
(1) 做this检查。如果是this则返回true;
(2) 做null检查。如果是null则返回false;
(3) 做instanceof检查。如果instanceof 返回false,那么equals方法就返回false。在做了一些研究之后,我发现可以用getClass()方法代替instanceof 来比较类型的相等性,因为instanceof 在检查子类的时候也会返回true。所以在需要商业逻辑的时候它并不是一个严格的相等关系。如果你的类是不变类,没有类会继承它,那么使用instanceof 时合适的。所以可以使用一下方式代替instanceof :
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
(4) 对象类型转换。
(5) 以数值型属性开始比较每个属性值,因为数值型属性比较最快而且在结合检查的时候可以使用短路操作。如果第一个属性不匹配,那么就可以返回false而不需要匹配剩余的属性。在每个属性调用equals方法前也要记得做null检查,避免在递归equals检查时出现NullPointerException。
/**
* Person class with equals and hashcode implementation in Java
* @author Javin Paul
*/
public class Person {
private int id;
private String firstName;
private String lastName;
public int getId() { return id; }
public void setId(int 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; }
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
Person guest = (Person) obj;
return id == guest.id
&& (firstName == guest.firstName
|| (firstName != null && firstName.equals(guest.getFirstName())))
&& (lastName == guest.lastName
|| (lastName != null && lastName .equals(guest.getLastName())));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + id;
result = prime * result
+ ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
}
在覆写
equals时的常见错误
(1) 重载了equals方法而没有重写它
这是我见到的覆写equals方法最常见的错误。equasl的语法是public boolean equals(Object obj),但许多人无意间重载了equals方法 public boolean equals(Person obj)。这个错误因为静态绑定而非常难以察觉。
(2) 第二个错误是在覆写equals方法时没有为成员变量没有做null检查,最终在调用equals方法时导致 NullPointerException。正确的做法是:
firstname == guest.firstname || (firstname != null && firstname.equals(guest.firstname)));
(3) 只覆写equals()方法而没有覆写hashCode方法。你必须同时覆写equals和hashCode的方法,要不然这个对象就不能在HashMap中作为一个key,因为HashMap是依赖这两个方法的。
(4) 最后一个错误是在覆写equals方法的时候没有保持equals方法和compareTo()的一致性,这不是正常的要求,只是为了服从Set的约定避免重复。SortedSet 的实现例如TreeSet使用compareTo方法来比较两个对象,例如字符串。如果compareTo方法和equals方法没有保持一致,那么TreeSet就会允许重复,这样就损坏了Set不能重复的约定。想要学习更多可以查看Things to remember whileoverriding compareTo in Java
1)大多数的IDE,例如NetBeans, Eclipse 和 IntelliJ IDEA提供生成equals和hashCode方法的支持。在In Eclipse do the right click-> source-> generate hashCode() and equals().
2)如果你的类中有一些唯一的商业主键,那么在equals方法中比较这些主键字段就足够了而不需要比较所有的字段。例如“id”是每个Person唯一的,那么只需要比较id就可以鉴别两个Person是不是相同的。
3)在覆写hashCode方法时,要确保你使用的所有字段都是在equals方法里是相同的。
4)String和包装类例如Integer,Float和Double需要覆写equals方法而StringBuffer不需要。
5)只要有可能,要努力通过final使你的字段不可变。Equals方法在不可变字段上要比可变字段安全的多。