《Effective Java》第2版的第9条条款要求“覆盖equals方法的类总要覆盖hashCode方法”,如果不这么做,就违反了Object.hashCode() 的通用约定,导致该类的实例无法与基于散列的集合(HashMap, HashSet, Hashtable)一起使用。
约定的内容包括:
1. 应用程序执行期间,只要对象equals方法的比较操作使用的信息没有被修改,前后调用hashCode的到的值应保持不变
2. 两个对象根据equals(Object)方法比较是相等的,则它们分别调用hashCode得到的值也应相等
3. 两个对象根据equals(Object)方法比较不相等,则它们的的hashCode返回的值不要求一定不相等
基于散列的集合通过hashCode方法计算得到的哈希码确定需要存放数据的散列桶,根据上述第3条描述,逻辑上不相等的对象有可能会产生相等的哈希码,所以哈希表有可能冲突,Java中的HashMap通过链地址法(链表)处理冲突。
如果某个类覆盖了equals方法,但没有覆盖hashCode方法,当该类的实例作为HashMap的key时,会出现问题:
import java.util.HashMap;
import java.util.Map;
public class PhoneNumber {
private final int areaCode;
private final int prefix;
private final int lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber){
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
@Override
public boolean equals(Object o){
if(this == o)
return true;
if(!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return this.areaCode == pn.areaCode
&& this.prefix == pn.prefix
&& this.lineNumber == pn.lineNumber;
}
public static void main(String[] args){
Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
map.put(new PhoneNumber(707, 102, 888), "Jenny");
System.out.println(map.get(new PhoneNumber(707, 102, 888)));
}
}
上述代码的执行结果是:null
为什么会出现这样的结果?因为PhoneNumber类并没有覆盖hashCode方法,所以会导致前后两个new PhoneNumber(707, 102, 888)的哈希码是完全没有关系的两个随机数,因此,后面从map中取数据时,也就不能获取存放之前数据的那个散列桶,所以无法获取希望的返回值“Jenny”;要解决这一问题,还需要重写hashCode方法。
那么怎样编写一个正确且有效的hashCode方法呢?正确性要求逻辑相等的对象,他们的hashCode必须返回相等的值;而有效性则要求不相等的对象尽可能获得不同的返回值。《Effective Java》中介绍了一种常用的方法:首先把某个整形常数值保存在result中;然后使用公式: result = result * 31 + c;c的计算方法如下:如果域的类型为byte, char, short, int,直接返回;如果为boolean,返回1或0;如果为float,返回Float.floatToIntBits(f);如果为long,返回前32位和后32位亦或操作后的返回值;如果为double,返回Double.doubleToIntBits(d)及long类型的操作后的值;对于引用类型,则递归调用下去。对于上述PhoneNumber的类型,其hashCode的定义如下:
@Override
public int hashCode(){
int result = 17;
result = result * 31 + areaCode;
result = result * 31 + prefix;
result = result * 31 + lineNumber;
return result;
}
添加上述代码后返回:Jenny
注:计算散列码时不要为了性能而忽略某个关键字段,虽然计算过程的效率提高了,但有可能会因此而增大散列冲突的概率,从而影响整体的效率