1.为什么覆盖equals()时总要覆盖hashCode()?
如果不这样做的话,就会违反了Object.hashCode()的通用约定。
通用约定如下:
- 只要对象的equals()方法的比较操作所用到的信息没有被修改,那么多洗调用hashCode()方法都必须返回同一个整数。
- 如果两个对象equals()判断相等,那么其hashCode()返回值也相等。
- 如果两个对象hashCode()返回值相等,那么equals()判断不一定相等(尽可能的满足不同对象hashCode不一致,有可能提高散列表的性能)。
2.覆盖equals()而未覆盖hashCode的结果
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode,int prefix,int lineNumber) {
this.areaCode=(short)areaCode;
this.prefix=(short)prefix;
this.lineNumber=(short)lineNumber;
}
@Override//实现对象的逻辑相等
public boolean equals(Object obj) {
if(obj==this) return true;
if(!(obj instanceof PhoneNumber))
return false;
PhoneNumber pn=(PhoneNumber)obj;
return pn.areaCode ==areaCode
&&pn.prefix == prefix
&&pn.lineNumber == lineNumber;
}
/*这里将自己实现的hashCode注释,测试的时候是没有这个方法的
@Override //正确的hashCode()方法重写
public int hashCode() {
int result=17;
result =31*result + areaCode;
result =31*result + prefix;
result =31*result + lineNumber;
return result;
}
*/
}
测试代码如下:
import hashCode.PhoneNumber;
//如果你不明白哈希表的工作原理,请自行百度
public class HashCodeMain {
public static void main(String[] args) {
Map<PhoneNumber, String> map=new HashMap<PhoneNumber, String>();
PhoneNumber pn=new PhoneNumber(707, 867, 5309);
map.put(pn, "Jenny");
map.get(new PhoneNumber(707, 867, 5309));//null
map.get(pn);//Jenny
}
}
上面的结果违反了第二条约定:相等的对象必须具有相等的散列值;此处的相等是根据equals()方法的逻辑相等,而默认的散列值却被实现成了与对象地址相关。可见equals()方法的逻辑相等与hashCode()方法的地址相等发生了冲突。
如果我们取消hashCode()方法的注释,那么这个测试代码的结果就会变成两个Jenny,而这才是我们想要的结果。
3.如何正确的覆盖hashCode()?
- 把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中
- 对于对象中每个关键域f(指equals()方法中涉及的每个域),完成以下步骤:
a.为该域计算int类型的散列码c:
1.如果该域是boolean,则计算(f ? 1:0)。
2.如果该域是byte,char,short,或int类型,则计算(int)f。
3.如果该域是long类型,则计算(int)(f^(f>>>32))。
4.如果该域是float类型,则计算Float.floatToBits(f)。
5.如果该域是double类型,则计算Double.doubleToLongBits(f),然后按照步骤a.3,为得到的long类型计算散列值。
6.如果该域是一个对象引用,并且该类的equals()方法通过递归的调用equals()的方式来比较这个域,则同样为这个域递归地调用hashCode();如果这个域的值为null,则返回0。
7.如果该域是一个数组,则要把每一个元素当做单独的域来处理。可以递归地引用上述规则。如果数组中的每个元素都很重要,可以使用JDK1.5增加的方法Arrays.hashCode()方法。
`public static int hashCode(long a[]) {
if (a == null)
return 0;
int result = 1;
for (long element : a) {
int elementHash = (int)(element ^ (element >>> 32));
result = 31 * result + elementHash;
}
return result;
}`
b.按照下面的公式,把步骤a中计算得到的散列码c合并到result中:
result =31 *result + c;
- 返回result(如果一个类是不可变的,并且计算散列码的开销也比较大,就应该考虑把散列码作为属性存在对象内部,而不是每次请求的时候都重新计算散列码)。
参考资料:
《Effective Java》