覆盖equals时总要覆盖hashcode方法
1、这样干的原因是什么?
如果这个类仅仅是重写了equals方法而没有重写hashCode,那么这个类和基于散列的集合类一起工作时就会出现问题。这样的集合包括HashMap,HashSet以及Hashtable。
比如这种情况:
public final class PhoneNumber {
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
}
Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
输出结果为:null
//
这就出错了,对象相等,但是hashCode不相等,所以取不到值。
为什么会出现这种情况呢,下面我们看一看Map的源码就清楚了:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab;
Node<K,V> first, e;
int n;
K k;
/**
* 检查table是否为空,table为HashMap实例中的一个存放数据的数组
* 检查第一个元素的hash码是否为null
*/
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
/*
* 效率方面的考虑
* 总是将第一个对象拿出来比较,如果符合要求直接返回
* 避免执行循环准备工作的一系列代码
*/
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//进入循环之前的一系列准备以及检查工作
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
//循环内部利用&&运算符的惰性,如果hash码不相同就直接跳过了= =
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
put方法把电话号码对象存放在一个散列桶(hash bucket)中,get方法却在另外一个散列桶里面查找。即使这两个实例正好被放到了同一个散列桶里面,get方法也一定会返回null,因为HashMap做了一项优化,将每个项相关联的散列码存放起来,如果hash码不匹配,则不会去检验对象的等同性,充分利用了逻辑与运算的惰性(&&)。
2、规范
两个对象使用equals返回true,则它们的hashCode也一定相等;如果两个对象的hashCode相等,则它们的equals则不一定相等。
Object规范[JavaSE6]:
在应用程序的一次执行期间,只要对象的equals方法的比较操作所用的信息没有被修改,那么对这个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致
如果两个对象根据equals(Object)方法比较结果相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法不一定要产生不一样的整数结果。但是我们应该知道,给不相等的对象产生不相同的整数结果,可以提高散列表(hashtable)的性能。
3、实现hashCode
如何实现hashCode,当然你可以使hashCode返回一个固定的数值,任何对象的hashCode都是一个固定的数值,这没有问题。但当它与基于散列的集合类一起工作时,这些元素将具有相同的散列码,进而使得所有对象都被映射到统一散列桶中,使得散列表退化为链表。具体重写hashCode的算法不在赘述。
始终要覆盖toString
为什么这么干?
原因在于在有的场景下会打印一条日志,日志的内容就是POJO类的属性字段值,这个时候toString的意义很明显的就体现出来了