转自:http://tantanit.com/java-always-override-hashcode-when-override-equals/
最重要的是第二点,相等的对象必须有相同的hashCode,由于默认的hashCode方法针对每一个对象返回一个固定的随机值(有的实现是根据对象地址返回值,相当于每一个对象对应一个固定的随机值),所以当我们使用equals方法的同时,必须override(重写)hashCode方法,以满足这一点。
public class TantanitReaderPhone {
private String areaCode;
private String localNumber;
public TantanitReaderPhone(String areaCode, String localNumber) {
this.areaCode = areaCode;
this.localNumber = localNumber;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof TantanitReaderPhone))
return false;
TantanitReaderPhone tantanitReaderPhone = (TantanitReaderPhone)obj;
return areaCode.equals(tantanitReaderPhone.areaCode)
&& localNumber.equals(tantanitReaderPhone.localNumber);
}
public static void main(String[] args) {
Map tantanitReaderPhoneStringMap
= new HashMap<>();
tantanitReaderPhoneStringMap.put(
new TantanitReaderPhone("86","13200001234"),"张三"
);
String name=tantanitReaderPhoneStringMap.get(
new TantanitReaderPhone("86","13200001234")
);
if(name==null){
System.out.print("name is null");
}else {
System.out.print(name);
}
}
}
上面的代码是一个手机号码的例子,手机号码由区号(比如中国是86)和本国手机号构成。我们重写了equals方法,但hashCode使用的仍然是父类也就是Object类的方法,可以理解为是一个随机数。在main函数中,我们定义了一个以TantanitReaderPhone为key的hashMap,保存并试图取出一个value值。需要注意的是,我们保存和取出时,使用的是两个不同的对象(两次都是new一个新的对象),但两个对象有着相同的areaCode和localNumber,根据我们重写的equals方法,这两个对象是相等的。但是由于我们没有重写hashCode方法,这两个对象的哈希值不同,所以使用第二个对象无法在hashMap里找到第一次存进去的值。
这是因为,哈希表的每个分区,只会对应有限的哈希值,并存储这些哈希值对应的对象。所以哈希值不同,首先找不到对应的分区,即使碰巧哈希表有分区同时对应着两个哈希值,由于哈希表往往会进行优化,对哈希值先进行判断,所以不相等的哈希值找不到对应的对象。
所以,当需要根据哈希值进行存储时,应该重写hashCode方法,根据字段值生成对应的hashCode(哈希值)。下面讲解如何计算hash值,并且改写我们上面的例子,重写hashCode方法,再执行以下main函数,看看有什么结果。
而要如和计算这个字段的值filedHashValue值呢,根据字段类型分为三种情况,一种是基础数据(比如int,boolean,),一种是对象,还有一种是数组。
如果字段是基础数据,假设数值为f,根据类型
而如果字段是对象,如果是null,返回0,否则根据上述【如何计算hashCode】中所述步骤1和2,计算这个对象的hashCode,作为这个字段的值。实际上是一个递归的过程。
而如果字段是数组,则对每一个元素进行计算,得到filedHashValue,并执行result = 31 * result + filedHashValue;
这里补充一下HashMap对象是如何计算哈希值的,JDK中,HashMap类自身已经实现了hashCode方法。一个hashMap其实是entry的数组。
AbstractMap中,hashCode对数组中的每一个entry计算哈希值,并得到所有哈希值的和。
public int hashCode() {
int h = 0;
Iterator> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
而每一个entry按照以下方式计算哈希值(JDK1.7之后的版本)。
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
这个实现非常巧妙,由于entry由key和value生成,所以将两个哈希值求与,既保证具有相同key和value的entry一定具有相同的哈希值,又保证了效率(与操作的效率很高)。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode.hashCode();
result = 31 * result + localNumber.hashCode();
return result;
}
由于areaCode和localNumber都是用来区分TantanitReaderPhone的重要字段,所以根据这两个字段来计算哈希值。这两个字段都是String类型,直接调用String自带的hashCode方法(areaCode和localNumber假定都不为null)。
将TantanitReaderPhone的hashCode方法按照上述代码进行重写后,再执行main函数,打印出“张三”,成功从hashMap中取出对应的值了。您可以复制一下TantanitReaderPhone的代码,亲自试试。