日期:2014-05-20  浏览次数:20760 次

请教hashCode的本质
我想请问一下hashCode到底代表什么东西呢,对象的hashcode和内存地址有什么关系啊?
Java code

package wukun;   
  
public class Stu {   
    private String name;   
    private String sex;   
       
    public Stu(String name, String sex) {   
        super();   
        this.name = name;   
        this.sex = sex;   
    }   
}  

package wukun;   
  
import java.util.ArrayList;   
import java.util.HashSet;   
import java.util.Set;   
  
public class HashCodeTest {   
       
    public static void main(String[] args) {   
           
//      Set h1 = new HashSet();   
//      Set h2 = new HashSet();   
//         
//      ArrayList a1 = new ArrayList();   
//      ArrayList a2 = new ArrayList();   
//         
////        a1.add(new String("abc"));     
////        a2.add(new String("abc"));   
//         
//      h1.add(a1);   
//      h2.add(a2);   
//         
//      System.out.println(h1.hashCode());   
//      System.out.println(h2.hashCode());   
//         
        Stu s1 = new Stu("wukun","男");   
        Stu s2 = new Stu("wukun","男");   
           
        System.out.println(s1.hashCode());   
        System.out.println(s2.hashCode());   
           
    }   
} 

为什么2个ArrayList的hash码一样,而2个自定义的stu对象的hash码就不一样呢?(主要问这个问题)







以下内容说明了为什么要重写equals和hashCode方法,供大家参考。
这是jdk1.5中hashmap中put方法的源代码,
Java code

public V put(K key, V value) {
K k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, k, value, i);
return null;
}


hashset的底层实际上是hashmap,通过一个entry数组来是实现的,数组的每一个元素又是一个链表,

eq(k, e.key)) 方法我看明白了,它的具体内容是这样的
Java code

static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}



这里的k是我们要存储的对象的引用,我错误的以为eq方法调用了object的equals方法,导致方法永远返回false,其实这里参数对象Object x虽然是Object的引用,但是它实际上指向我们外部一个实例化的子类,就是我们要往hashset中存的那个对象,这就很明显的告诉了我们为什么要重写equles方法了,当我们重写了equles方法以后,它就会去调用我们子类重写的那个方法,而不是调用Object的equels方法。
如果比较的结果相等,就代表hashset中已经存在了这个对象了,执行if语句里面的内容,这里的value好像没多大用处,它是一个没有内容的Object对象,但是当我们使用hashmap时,就是有value的,当保存一个key重复,但是value不重复的对象到hashmap中去的时候,这个key不变,而value被覆盖。

当for循环结束走到尽头时,表示hashset中没有这个对象,addEntry(hash, k, value, i);就会执行,它的源代码如下:
这个方法很好理解,它相当与把新内容插到了table【i】的头节点,原来的头节点变成了第2个。
Java code

void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}



resize(2 * table.length);相当扩充容量,这个就不用解释了,我也没细看。




------解决方案--------------------
因为ArrayList的父类AbstractList重写了hashcode()方法,根据列表内容算hashcode,而你两个ArrayList里面放的字符串内容一致,
当然hashcode就一样啦!
至于你Stu类没重写hashcode()方法,所以使用的是Object类的hashcode()方法,默认输出对象地址,两个不同对象的地址当然不一致
------解决方案--------------------
JDK规范里建议用对象在虚拟机中的地址作为hashCode计算出来的值,当然hash结构的本质意义是能更快的搜索到元素,这里也就是能帮助虚拟机更快的定位到虚拟机管理的对象,你的问题是:
为什么2个ArrayList的hash码一样,而2个自定义的stu对象的hash码就不一样呢?

因为ArrayList的hashCode方法被重写了,重写的代码在ArrayList的父类AbstractList里面,重写的逻辑大概是以元素的hashCode为值来计算,而你ArrayList里装的元素又都是"abc"这个String,虽然是两个内容相同的String对象,但String对象的hashCode方法也被重写了,以内容为原始值来就算出来的,所以简而言之,相等的原因是: