基本摘抄自Java 中正确使用 hashCode 和 equals 方法
hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法。
1.equals
equals要遵守的通用约定(equals方法实现了等价关系):
1)自反性:x.equals(x)一定返回true
2)对称性:x.equals(y)返回true当且仅当y.equals(x)
3)传递性:x.equals(y)且y.equals(z),则x.equals(z)为true
4)一致性:若x.equals(y)返回true,则不改变x,y时多次调用x.equals(y)都返回true
5)对于任意的非空引用值x,x.equals(null)一定返回false。
当重写完equals方法后,应该检查是否满足对称性、传递性、一致性。(自反性、null通常会自行满足)
1.1 equals重写默认实现
有的时候程序要求我们必须改变一些对象的默认实现。
来看看这个例子,让我们创建一个简单的类Person
public class Person {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "id:"+this.getId()+" name:"+this.getName();
}
}
上面的Person类只是有一些非常基础的属性和getter、setter.现在来考虑一个你需要比较两个person的情形。
public class EqualTest {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.setId("1"); p1.setName("xwq");
p2.setId("1"); p2.setName("xwq");
System.out.println(p1.equals(p2));
}
}
毫无疑问,上面的程序将输出false,但是,事实上上面两个对象代表的是通过一个person。真正的商业逻辑希望我们返回true。
为了达到这个目的,我们需要重写equals方法。
@Override
public boolean equals(Object o) {
if(o == null) return false;
if(o == this) return true;//提高效率
if(o.getClass() != this.getClass())
return false;
//或者使用instance关键字判断
// if(!(o instanceof Person))
// return false;
Person p1 = (Person)o;
//如果元素是数组,可使用Arrays.deepEquals(array1,array2);进行判断
return this.getId().equals(p1.getId()) && this.getName().equals(p1.getName());
}
在上面的类中添加这个方法,EauqlsTest将会输出true。
1.2 编写equals开发方法 的建议
当equals参数不属于同一类时,且具有继承关系时,instanceof的检测结果将不满足对称性。
如:c是p的子类,如果在equals中用instanceof检测,那么:
p.equals(c) 将返回true;
c.equals(p) 将返回false或者抛出异常。
1、显示参数命名为otherobject,稍后强制转换为叫other的变量。
2、检测this==otherobject
3、检测this==null
4、检测getclass()!=otherobject.getclass()
如果所有的子类都拥有统一的语义,就使用instanceof检测
otherobject instanceof classname
5、将otherobject转换为相应类型的变量
6、比较所有的域
7、若在子类中重新定义了equals开发方法 ,则需在子类中包含调用super.equals(other)
2.hashCode
hashCode()是HashTable、HashMap和HashSet使用的。hashCode()方法被用来获取给定对象的唯一整数。这个整数被用来确定对象被存储在HashTable类似的结构中的位置。默认的,Object类的hashCode()方法返回这个对象存储的内存地址的编号。
hash散列算法,使得在hash表中查找一个记录速度变O(1). 每个记录都有自己的hashcode,散列算法按照hashcode把记录放置在合适的位置. 在查找一个记录,首先先通过hashcode快速定位记录的位置.然后再通过equals来比较是否相等。如果hashcode没找到,则必定不equal,元素不存在于哈希表中;即使找到了,也只需执行hashcode相同的几个元素的equal,如果不equal,还是不存在哈希表中。
2.1 hashcode重写默认实现
来看看这个例子,
import java.util.HashSet;
import java.util.Set;
public class EqualTest {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.setId("1"); p1.setName("xwq");
p2.setId("1"); p2.setName("xwq");
Set<Person> s = new HashSet<Person>();
s.add(p1);
s.add(p2);
System.out.println(s);
}
}
上面的程序输出的结果是两个。
如果两个employee对象equals返回true,Set中应该只存储一个对象才对,问题在哪里呢?
我们忘掉了第二个重要的方法hashCode(),因为hashCode()的默认实现是对象在内存中的内存地址,所以p1和p2都被插入到HashSet中。
如果重写equals()方法必须要重写hashCode()方法。我们加上下面这个方法,程序将执行正确。
需要注意记住的事情
- 尽量保证使用对象的同一个属性来生成hashCode()和equals()两个方法。在我们的案例中,我们使用人员id。
- eqauls方法必须保证一致(如果对象没有被修改,equals应该返回相同的值)
- 任何时候只要a.equals(b),那么a.hashCode()必须和b.hashCode()相等。
- 两者必须同时重写。
当使用ORM的时候特别要注意的
- 如果你使用ORM处理一些对象的话,你要确保在hashCode()和equals()对象中使用getter和setter而不是直接引用成员变量。因为在ORM中有的时候成员变量会被延时加载,这些变量只有当getter方法被调用的时候才真正可用。
- 例如在我们的例子中,如果我们使用p1.id == p2.id则可能会出现这个问题,但是我们使用p1.getId() == p2.getId()就不会出现这个问题。