zoukankan      html  css  js  c++  java
  • Java的HashCode,Equal和==

    ----------------------------------------以下是分割线----------------------------------------

    我们都知道Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类。Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。

    java中比较两个对象不是用“==”吗?对于String a=”hello”;String b=”hello”,比较两个字符串是否相等,我们都知道必须用equals方法,而不是用==。 因为a,b是内存中两个不同的对象,他们指向两个内存中不同的地址,只是两个地址存的内容都相同罢了。如果用==比较,则比较的是两个对象的地址,很显然这里返回false,不是我们需要的。如果想返回true,就必须使用equals方法了。在这里String类里对父类Ojbect的equals方法进行了重写(见String类里的equals方法),比较的不再是地址,而是地址里所存的字符串的值。如果不对equals方法重写,调用的就是父类的equals方法,比较的还是两个对象的地址,同调用==一样。

    对于值对象,==比较的是两个对象的值,对于引用对象,比较的是两个对象的地址。默认的equals方法同==,一般来说我们的对象都是引用对象,要重写equals方法。再举一个例子,现在有一个学生对象,有属性学号跟姓名,现在我新建了一个学生对象,又从数据里查出一个学生对象,这两个对象的学号跟姓名都一样,那这两个对象是不是相等呢?一般情况下,除非你有特殊需求要处理,这两个对象是相等的,可如果用==去比较,返回的结果是错误的。这时候我们就必须重写equlas方法了。如果学号是主键,在equals方法里,我们认为只要学号相同,就可以返回true,具体实现建议参考String类里equals书写风格。

    hashCode方法也是可以用来比较两个对象是否相等的。但是我们很少使用,应该说是很少直接使用。hashCode方法返回的是一个int值,可以看做是一个对象的唯一编码,如果两个对象的hashCode值相同,我们应该认为这两个对象是同一个对象。一般如果使用java中的Map对象进行存储时,他会自动调用hashCode方法来比较两个对象是否相等。所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。如上面的学生例子,如果学号相同,不管姓名相不相同,返回的hash值一定要是一样的,这时我们的hash值只与学号有关。

    String类中的equals方法 与hashCode方法:

    ----------------------------------------------------分割线2------------------------------------------------

    1. 首先equals()和hashcode()这两个方法都是从object类中继承过来的。


    equals()方法在object类中定义如下:

    1. public boolean equals(Object obj) {   
    2. return (this == obj);   
    3. }   
    public boolean equals(Object obj) { 
    return (this == obj); 
    } 


    很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。但是我们必需清楚,当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。

    比如在String类中如下:

    1.    
    2. public boolean equals(Object anObject) {   
    3. if (this == anObject) {   
    4. return true;   
    5. }   
    6. if (anObject instanceof String) {   
    7. String anotherString = (String)anObject;   
    8. int n = count;   
    9. if (n == anotherString.count) {   
    10. char v1[] = value;   
    11. char v2[] = anotherString.value;   
    12. int i = offset;   
    13. int j = anotherString.offset;   
    14. while (n-- != 0) {   
    15. if (v1[i++] != v2[j++])   
    16. return false;   
    17. }   
    18. return true;   
    19. }   
    20. }   
    21. return false;   
    22. }   
     
    public boolean equals(Object anObject) { 
    if (this == anObject) { 
    return true; 
    } 
    if (anObject instanceof String) { 
    String anotherString = (String)anObject; 
    int n = count; 
    if (n == anotherString.count) { 
    char v1[] = value; 
    char v2[] = anotherString.value; 
    int i = offset; 
    int j = anotherString.offset; 
    while (n-- != 0) { 
    if (v1[i++] != v2[j++]) 
    return false; 
    } 
    return true; 
    } 
    } 
    return false; 
    } 
    


    很明显,这是进行的内容比较,而已经不再是地址的比较。依次类推Double、Integer、Math。。。。等等这些类都是重写了equals()方法的,从而进行的是内容的比较。当然了基本类型是进行值的比较,这个没有什么好说的。


    我们还应该注意,Java语言对equals()的要求如下,这些要求是必须遵循的:
    • 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
    • 反射性:x.equals(x)必须返回是“true”。
    • 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
    • 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
    • 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
    以上这五点是重写equals()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。


    2. 其次是hashcode() 方法,在object类中定义如下:

    1. public native int hashCode();   
    public native int hashCode(); 


    说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()方法,比如String、Integer、Double。。。。等等这些类都是覆盖了hashcode()方法的。

    例如在String类中定义的hashcode()方法如下:

    1. public int hashCode() {   
    2. int h = hash;   
    3. if (h == 0) {   
    4. int off = offset;   
    5. char val[] = value;   
    6. int len = count;   
    7.   
    8. for (int i = 0; i < len; i++) {   
    9. h = 31*h + val[off++];   
    10. }   
    11. hash = h;   
    12. }   
    13. return h;   
    14. }   
    public int hashCode() { 
    int h = hash; 
    if (h == 0) { 
    int off = offset; 
    char val[] = value; 
    int len = count; 
    
    for (int i = 0; i < len; i++) { 
    h = 31*h + val[off++]; 
    } 
    hash = h; 
    } 
    return h; 
    } 


    解释一下这个程序(String的API中写到):
    s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
    使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。)

    3.这里我们首先要明白一个问题:
    1:equals()相等的两个对象,hashcode()一定相等;
    2:equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。

    换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)。
    反过来:

    3:hashcode()不等,一定能推出equals()也不等;
    4:hashcode()相等,equals()可能相等,也可能不等。

    解释下第3点的使用范围,

    我的理解是在object、String等类中都能使用。

    在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了;

    在String类中,equals()返回的是两个对象内容的比较,当两个对象内容相等时, Hashcode()方法根据String类的重写(第2点里面已经分析了)代码的分析,也可知道hashcode()返回结果也会相等。

    以此类推,可以知道Integer、Double等封装类中经过重写的equals()和hashcode()方法也同样适合于这个原则。当然没有经过重写的类,在继承了object类的equals()和hashcode()方法后,也会遵守这个原则。

    4.谈到hashcode()和equals()就不能不说到hashset,hashmap,hashtable中的使用,具体是怎样呢,请看如下分析:


    Hashset是继承Set接口,Set接口又实现Collection接口,这是层次关系。那么hashset是根据什么原理来存取对象的呢?
    在hashset中不允许出现重复对象,元素的位置也是不确定的。在hashset中又是怎样判定元素是否重复的呢?

    在java的集合中,判断两个对象是否相等的规则是:
    1),判断两个对象的hashCode是否相等

    如果不相等,认为两个对象也不相等,完毕
    如果相等,转入2
    (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。后面会重点讲到这个问题。)

     
    2),判断两个对象用equals运算是否相等
    如果不相等,认为两个对象也不相等
    如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

    为什么是两条准则,难道用第一条不行吗?
    不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。
    比如下面的代码:

    1. public static void main(String args[]){   
    2. String s1=new String("zhaoxudong");   
    3. String s2=new String("zhaoxudong");   
    4. System.out.println(s1==s2);//false    
    5. System.out.println(s1.equals(s2));//true    
    6. System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode()    
    7. System.out.println(s2.hashCode());   
    8. Set hashset=new HashSet();   
    9. hashset.add(s1);   
    10. hashset.add(s2);   
    11. /*实质上在添加s1,s2时,运用上面说到的两点准则,可以知道hashset认为s1和s2是相等的,是在添加重复元素,所以让s2覆盖了s1;*/   
    12. Iterator it=hashset.iterator();   
    13. while(it.hasNext())   
    14. {   
    15. System.out.println(it.next());   
    16. }   
    public static void main(String args[]){ 
    String s1=new String("zhaoxudong"); 
    String s2=new String("zhaoxudong"); 
    System.out.println(s1==s2);//false 
    System.out.println(s1.equals(s2));//true 
    System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode() 
    System.out.println(s2.hashCode()); 
    Set hashset=new HashSet(); 
    hashset.add(s1); 
    hashset.add(s2); 
    /*实质上在添加s1,s2时,运用上面说到的两点准则,可以知道hashset认为s1和s2是相等的,是在添加重复元素,所以让s2覆盖了s1;*/ 
    Iterator it=hashset.iterator(); 
    while(it.hasNext()) 
    { 
    System.out.println(it.next()); 
    } 


    最后在while循环的时候只打印出了一个”zhaoxudong”。
    输出结果为:false
    true
    -967303459
    -967303459
    这是因为String类已经重写了equals()方法和hashcode()方法,所以在根据上面的第1.2条原则判定时,hashset认为它们是相等的对象,进行了重复添加。

    但是看下面的程序:

    1. import java.util.*;   
    2. public class HashSetTest   
    3. {   
    4. public static void main(String[] args)   
    5. {   
    6. HashSet hs=new HashSet();   
    7. hs.add(new Student(1,"zhangsan"));   
    8. hs.add(new Student(2,"lisi"));   
    9. hs.add(new Student(3,"wangwu"));   
    10. hs.add(new Student(1,"zhangsan"));   
    11.   
    12. Iterator it=hs.iterator();   
    13. while(it.hasNext())   
    14. {   
    15. System.out.println(it.next());   
    16. }   
    17. }   
    18. }   
    19. class Student   
    20. {   
    21. int num;   
    22. String name;   
    23. Student(int num,String name)   
    24. {   
    25. this.num=num;   
    26. this.name=name;   
    27. }   
    28. public String toString()   
    29. {   
    30. return num+":"+name;   
    31. }   
    32. }   
    import java.util.*; 
    public class HashSetTest 
    { 
    public static void main(String[] args) 
    { 
    HashSet hs=new HashSet(); 
    hs.add(new Student(1,"zhangsan")); 
    hs.add(new Student(2,"lisi")); 
    hs.add(new Student(3,"wangwu")); 
    hs.add(new Student(1,"zhangsan")); 
    
    Iterator it=hs.iterator(); 
    while(it.hasNext()) 
    { 
    System.out.println(it.next()); 
    } 
    } 
    } 
    class Student 
    { 
    int num; 
    String name; 
    Student(int num,String name) 
    { 
    this.num=num; 
    this.name=name; 
    } 
    public String toString() 
    { 
    return num+":"+name; 
    } 
    } 
    

    输出结果为:
    1:zhangsan
    1:zhangsan
    3:wangwu
    2:lisi


    问题出现了,为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有


    因为在根据hashcode()对两次建立的new Student(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等(这个不用解释了吧)。
    那么为什么会生成不同的哈希码值呢?
    上面我们在比较s1和s2的时候不是生成了同样的哈希码吗?
    原因就在于我们自己写的Student类并没有重新自己的hashcode()和equals()方法,所以在比较时,是继承的object类中的hashcode()方法,呵呵,各位还记得object类中的hashcode()方法比较的是什么吧!!
    它是一个本地方法,比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了(这个大家都能理解吧。。。),造成的结果就是两个对象的hashcode()返回的值不一样。所以根据第一个准则,hashset会把它们当作不同的对象对待,自然也用不着第二个准则进行判定了。

    那么怎么解决这个问题呢??
    答案是:在Student类中重新hashcode()和equals()方法。


    例如:

    1. class Student   
    2. {   
    3. int num;   
    4. String name;   
    5. Student(int num,String name)   
    6. {   
    7. this.num=num;   
    8. this.name=name;   
    9. }   
    10. public int hashCode()   
    11. {   
    12. return num*name.hashCode();   
    13. }   
    14. public boolean equals(Object o)   
    15. {   
    16. Student s=(Student)o;   
    17. return num==s.num && name.equals(s.name);   
    18. }   
    19. public String toString()   
    20. {   
    21. return num+":"+name;   
    22. }   
    23. }   
    class Student 
    { 
    int num; 
    String name; 
    Student(int num,String name) 
    { 
    this.num=num; 
    this.name=name; 
    } 
    public int hashCode() 
    { 
    return num*name.hashCode(); 
    } 
    public boolean equals(Object o) 
    { 
    Student s=(Student)o; 
    return num==s.num && name.equals(s.name); 
    } 
    public String toString() 
    { 
    return num+":"+name; 
    } 
    } 
    


    根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,根据重写的方法hashcode(),获得的哈希码肯定是一样的(这一点应该没有疑问吧)。
    当然根据equals()方法我们也可判断是相同的。所以在向hashset集合中添加时把它们当作重复元素看待了。所以运行修改后的程序时,我们会发现运行结果是:
    1:zhangsan
    3:wangwu
    2:lisi
    可以看到重复元素的问题已经消除。


    关于在hibernate的pojo类中,重新equals()和hashcode()的问题:
    1),重点是equals,重写hashCode只是技术要求(为了提高效率)
    2),为什么要重写equals呢,因为在java的集合框架中,是通过equals来判断两个对象是否相等的
    3),在hibernate中,经常使用set集合来保存相关对象,而set集合是不允许重复的。我们再来谈谈前面提到在向hashset集合中添加元素时,怎样判断对象是否相同的准则,前面说了两条,其实只要重写equals()这一条也可以。


    但当hashset中元素比较多时,或者是重写的equals()方法比较复杂时,我们只用equals()方法进行比较判断,效率也会非常低,所以引入了hashcode()这个方法,只是为了提高效率,但是我觉得这是非常有必要的(所以我们在前面以两条准则来进行hashset的元素是否重复的判断)。
    比如可以这样写:

    1. public int hashCode(){   
    2. return 1;}//等价于hashcode无效   
    public int hashCode(){ 
    return 1;}//等价于hashcode无效 
    


    这样做的效果就是在比较哈希码的时候不能进行判断,因为每个对象返回的哈希码都是1,每次都必须要经过比较equals()方法后才能进行判断是否重复,这当然会引起效率的大大降低。

    ----------------------------------------The End----------------------------------------------

    转自:

    1:http://zhaoxudonglove.iteye.com/blog/257186

    2:http://cangzhitao.com/java/equals-hashcode.html

  • 相关阅读:
    POJ 1328 Radar Installation
    POJ 1700 Crossing River
    POJ 1700 Crossing River
    poj 3253 Fence Repair (贪心,优先队列)
    poj 3253 Fence Repair (贪心,优先队列)
    poj 3069 Saruman's Army(贪心)
    poj 3069 Saruman's Army(贪心)
    Redis 笔记与总结2 String 类型和 Hash 类型
    数据分析方法有哪些_数据分析方法
    数据分析方法有哪些_数据分析方法
  • 原文地址:https://www.cnblogs.com/WayneZeng/p/3360198.html
Copyright © 2011-2022 走看看