首先,int hashCode();是为了支持哈希表类的如HashMap,HashTable之类的底层使用了哈希表的类。
Java Object规范中int hashCode()方法的约定类容有三个:
(1) 只要对象的equals方法所用到的信息没有修改,那么hashCode方法必须始终如一的返回一个同一整数,在同一应用程序中多次执行中,每一次执行可以不一样。
(2) 如果两个对象的equals方法想到,那么每一个对象单独调用hashCode返回的数字必须相等。
(3) 如果两个对象的equals方法不相等,hashCode方法的返回值可以相等,给不同的对象返回不同的值可以提高哈希表的性能。
下面我们以HashMap为例来看一看为什么要做这样的约定。
下图是HashMap的底层数据结构:
图像来自http://zhangshixi.iteye.com/blog/672697
通过源码和上面的示意图我们可以知道HashMap的底层是一个数组,数组的每一个元素是一个Entry组织的链表。下面是HashMap的public V put(K key, V value)方法的源代码(1.7):
我们观察到被标记的两条语句,首先是通过hash(Object key)方法得到一个hash值,然后通过indexFor方法定位到hash值在数组中的位置。下面是hash(Object key)的源码。我们可以看出它调用了Key的hashCode()函数。
int indexFor(int h, int length)的源码:
static int indexFor(int h, int length) {
return h & (length-1);
}
从上面的内容我们知道我们用HashMap存储时是和Key的hash值相关的。如果hash值相同,那么定位数组的位置也相同(因为indexFor的返回值只和hash值和数组长度length有关,而数组的length只会在重散列时变化)
从前面我们知道数组的每一个元素都是Entry的链表,如果每一次hash值都相同,那么每一次都定位到数组的相同位置,那么链表就会很长,处理的时间也会很长。
而hash值是和Key的hashCode方法相关的,所以我们就可以理解hashCode的第三条约定了,给不同对象产生不同的hashCode可以提高哈希表的性能。
我们现在再来看一看为什么如果两个对象的equals方法相等,那么每一个对象单独调用hashCode方法必须返回相同的返回值。首先我们还是先看一看HashMap的取得的源代码:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
我们可以发现是可逆的,还是通过hash方法得到hash值,再通过indexFor方法定位元素在数组中的位置。想一想如果我们两个对象的equals方法相等,而hashCode方法的值可以不相等,那是不是就意味着两个逻辑上相同的对象可以放在不同的位置。那么是不是就意味着我们通过逻辑上相等的Key查找的是不同的地址(对象)。虽然这并没有什么错误,但是并不是我们实现HashMap的目的。下面通过一个例子简单的看一看会出现什么问题。
public class User
{
private long id;
private String name;
private String address;
private long phone;
@Override
public boolean equals(Object obj)
{
if(obj == this)
return true;
if(! (obj instanceof User))
return false;
User user = (User) obj;
return (user.id == id && user.phone == phone && name.equals(user.name) && address.equals(user.address));
}
public User(long id, String name, String address, long phone)
{
super();
this.id = id;
this.name = name;
this.address = address;
this.phone = phone;
}
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getAddress()
{
return address;
}
public void setAddress(String address)
{
this.address = address;
}
public long getPhone()
{
return phone;
}
public void setPhone(long phone)
{
this.phone = phone;
}
public static void main(String[] args)
{
HashMap<User, String> map = new HashMap<User,String>();
map.put(new User(1,"tom","china",13888888888L),"hello world");
System.out.println(map.get(new User(1,"tom","china",13888888888L)));
}
}
我们的本意是通过User来查找hello world,但是并没有如我们所愿对吗?为什么呢?因为我们重写了User的equals方法,但是没有重写hashCode方法,所以用的是继承自Object类的hashCode方法。因为是用new所以地址并不一样,hashCode的值自然也就不相同了。所以定位到了其他的位置,什么都没有找到返回null。