Java hashCode

Java equals与hashCode  

  equals与hashCode容易造成一个误解即:equals是比较对象的内容,而hashCode是返回对象地址。其实equals与hashCode关系非常紧密。打个比方,你家有5个人,那么5个人共用你们的家庭地址,对于邮寄员来说,他先找到你们家的地址(相当于根据hashCode返回),然后,一个个喊过去(调用equals方法),终于找到你这个对象了。equals和hashCode源于Object类。在原产地,equals只认为完完全全指向同一个对象,才是返回true的。hashCode是一种索引,通常用于提高大型数据集合的性能,尽管它有时可以被误解认为对象的ID号,这个错觉来自于Object类的hashCode返回值,它是VM虚拟地址转换而来的绝对唯一的整数。但是实际重写时它不必是唯一的,它也只有存储在对象集合里的时候,才会更多体现出它的价值,就像上段比方的家庭住址一样。虽然对象返回的hash值可能重复,但尽可能让他们平均的重复,就是一户家庭大概1-10个对象共用一个地址。如果对象是一样的,那么就严格要求,地址返回绝对一样,要不然,两个不同的朋友寄信给我,地址不一样,会是什么情况。所以实现hashCode方法最高准则就是:equals结果是true时,那么hashCode返回值必须是一样。

  上边有5个箱子,如果按一定规则塞入5个人,这个规则就是:首字母顺序,A-1 B-2 C-3 D-4 E-5,譬如说某个人要找Dogg,那么他直接奔第4个去就得了。可能另外又来了5个全是A开头的,那么非常不幸,全进了第一个箱子,根据hashCode返回,到第一个箱子,你不得不用equals方法一个个比对过去,才能找到那个人。所以说,好的规则尽量把对象均匀分散在箱子里,提高检索效率,这就是hashCode的使命所在,当然最懒的hashCode,它就是直接返回1,不管来什么对象都往第一个塞,虽然有些过分,但它没有违反合同,你能找到的两个相等的对象,必然返回同一个hashCode值。所以,"合法并不一定合情"。提高查找速度是哈希码的使命所在!像return 1这种不负责的做法,虽然对于查找没有任何帮助,但这样一个哈希码搞定所有对象方法还是不违反契约的。

  为了有更加深的认识,下面一段比较浅显的代码,但是可以比较明确地说明hashCode和equals是如何配合使用的:

/**
 * @author public
 *如果hashCode不存在,那么每项都进入自己的桶,而重写的equals方法将不起任何作用
 *如果hashCode存在,默认的hashCode返回值是唯一的。这里返回一律为9,那么第一项和第二项被看成完全重复的
 */
public class ToDos {
    String day;
    public ToDos(String d) {
        day=d;
    }
    
    public boolean equals(Object o) {
        return ((ToDos)o).day==this.day;
        
    }
    
    public int hashCode() {
        return 9;        
    }
    
}

 

public class MapEQ {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Map<ToDos, String> map=new HashMap<ToDos,String>();
        ToDos t1=new ToDos("Monday");
        ToDos t2=new ToDos("Monday");
        ToDos t3=new ToDos("Tuesday");
        
        map.put(t1, "doLaundry");
        map.put(t2, "payBills");
        map.put(t3, "cleanAttic");
        
        System.out.println(map.size());
        
    }

}

  如果在ToDos中重写equals和hashCode方法不存在,map.size()大小为3;如果重写equals方法不存在,hashCode方法存在,map.size()大小为2;如果重写equals方法和hashCode方法存在时,map.size()大小为3。

Java的哈希码及hashCode()方法

  一直以为hashCode()方法就是Java地址转换而来,这其实是一个误解。hashCode是native的方法,对地址进行处理得到的,无法对这个地址进行精确寻址,也更加无法操作这个地址,与地址挂钩最大的理由就是使每个对象返回不同的哈希码。各个类继承它时,可以重写该类的这个方法。例如:对于Integer类的hashCode()就一条语句:return value;只是返回的数据实际值,hashCode的计算方式越复杂,冲突的可能性越小。

  在Java应用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持保持一致。如果根据equals(Object)方法,两个对象是相等的,即equals方法返回true时,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数。如果根据equals(java.lang.Object)方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数。但是,为不相等的对象生成不同整数结果可以提高哈希表的性能。

  实际上,由Object类定义的hashCode方法确实会针对不同的对象返回不同的整数。这一般是通过将该对象的内部地址转换成一个整数来实现的,但是Java TM编程语言不需要这种实现技巧。要有效率的类必然会重写这个方法,重写时有两条原则:说到hashCode(),有必要提一下Hash开头的另几个类:

  HashCode是返回对象的哈希码,是对象的散列码。跟HashMap和Hashtable没有多大关系。它们两者是一种数据结构,以(值,关键字)形式存储数据值。HashTable是方法是线程同步的,HashMap不是线程同步,所以速度更快。HashMap中可以存在一条key或value为空的记录,Hashtable不可以。HashTable继承自Dictionary,HashMap继承自Map接口。

  不必对每个不同的对象都产生一个唯一的hashCode,只要你的HashCode方法使get()能够得到put()放进去的内容就可以了。即"不唯一原则"。生成hashCode的算法尽量使hashCode的值分散一些,不要很多hashCode都集中在一个范围内,这样有利于提高HashMap的性能。即"分散原则"。

  下面例子,是用来说明当equals返回为真时,hashCode必须一致,尽管可能违反分散原则,分散原则是期望,并不是一种硬性要求:

public class ValuePair {
       public int a =4,b;
       public boolean equals(Object other){
          try{
             ValuePair o = (ValuePair) other;
             return (a==o.a&&b==o.b)||(a==o.b&&b==o.a); //关键要清楚返回值为true时,说明什么?在另一个对象中,a和b值可以互换一下,本质上还是认为这两个对象是相等的。
          }catch(ClassCastException cce){
             return false;
          }
       }
       
       public int hashCode(){
          //请选择下边的答案(多选)
       }
}
A. return 0;
B. return a;
c. return a+b;
D. return a-b;
E. return a*b
F. retrun a^b
G. retrun (a<<16)|b;

  所谓的对象等同,在此题中,只有两个数据成员a和b,A、C、E均符合要求。特别指出的是,根据第二原则,A(0)也符合要求,但在某些场合下效果不好而以。这样的hashCode方法在逻辑上是没有问题的,但是在实际应用中,特别是在散列中使用时,是不合适的。  

posted @ 2014-10-16 14:47  楚藉  阅读(183)  评论(0)    收藏  举报