首先看Object对象中是如何定义equals 和 hashcode:
public boolean equals(Object obj) {
return (this == obj);
}
/*@return a hash code value for this object.*/
public native int hashCode();
可以看出,equals实际上是比较对象的内存地址,而hashCode是获取当前对象的hash值;
定义一个对象
public class Person {
private String name;
private int IDCard;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIDCard() {
return IDCard;
}
public void setIDCard(int IDCard) {
this.IDCard = IDCard;
}
public Person(String name, int IDCard) {
this.name = name;
this.IDCard = IDCard;
}
}
在现实中,如果两个人表示同一样人,那么他们的名字相同,身份证也一样,那么一定是一个人。
public static void main(String[] args) {
/*测试对象不需要重写hashcode*/
Person a = new Person("zhangsan" ,101);
Person b = new Person("zhangsan" ,101);
System.out.println(a.equals(b)); //false
}
此时可以看出 结果是 false, 因为上面基类中真实比较的其实是内存地址,与对象内容无关;如果要与对象产生关系,那么此时就需要重写 equals方法。
@Override
public boolean equals(Object obj) {
Person p = (Person)obj;
if(this.name == p.name && this.IDCard == p.IDCard){
return true;
}
return false;
}
此时再拿测试上面的main方法会发现,结果为true。
-------
那么什么时候需要重写hashCode 呢?
下面后一种情况,将人作为对象,年龄作为映射值:
public static void main(String[] args) {
/*测试对象不需要重写hashcode*/
Person a = new Person("zhangsan" ,101);
Person b = new Person("zhangsan" ,101);
System.out.println(a.equals(b));
System.out.println(a.hashCode()+"-"+b.hashCode());
/*测试对象需要重写hashcode*/
Map m = new HashMap<Person,Integer>();
m.put(a,10);
m.put(b,20);
System.out.println(m);
}
输出:{com.string.Person@2f92e0f4=10, com.string.Person@28a418fc=20}
会发现a、b明明是同一个人,map 应该存放的只有一个年龄才对,本来应该后一个覆盖前一个,为什么会打印出两个结果?
查看HashMap源码:
// 将“key-value”添加到HashMap中
public V put(K key, V value) {
// 若“key为null”,则将该键值对添加到table[0]中。
if (key == null)
return putForNullKey(value);
// 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 若“该key”对应的键值对不存在,则将“key-value”添加到table中
modCount++;
//将key-value添加到table[i]处
addEntry(hash, key, value, i);
return null;
}
这里的存的对象key其实是key的hashCode值,两个对象a、b地址是不一样的,自然会被认为是两个key值。
此时我们重写hashCode, 让hashCode值仅与name、IDCard有关,如下:
@Override
public int hashCode() {
int prime = 31;
int result = 1;
result = prime * result + name == null ? 0 : name.hashCode();
result = prime * result + IDCard;
return result;
}
测试上面main,输出结果:
{com.string.Person@15eb48fe=20}
只有后一种了,前面的结果被覆盖了!
所有要注意这里:要考虑到类似HashMap、HashTable、HashSet的这种散列的数据类型的运用。
补充:
哈希算法有一个协定:在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行hashcode比较时所用的信息没有被修改。
那么为什么要以上面那种方式重写hashCode呢?
String源码中对hashCode的重写给了我们启示:
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
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;
}
31可以拆解为2<<4 -1, 虚拟机做了相关优化,本身计算机乘除移位效率很高,这可能是用31的原因。
ref: