zoukankan      html  css  js  c++  java
  • java中equals方法和hashcode方法的区别和联系,以及为什么要重写这两个方法,不重写会怎样

    一、在Object类中的定义为:
    public native int hashCode();
    是一个本地方法,返回的对象的地址值。
    但是,同样的思路,在String等封装类中对此方法进行了重写。方法调用得到一个计算公式得到的 int值。

    二、在重写任何类得hashcode方法时必须遵循以下几点:
    1、在Java应用的同一次执行过程中,同一对象被多次调用,则他们的hashcode值必然相同。
    而对于同一个应用的两次不同的调用,它们的Hashcode值可以相同,也有可能不同。

    2、对于两个对象来说,如果他们的equals方法比较返回true,那么这两个对象的hashcode必然相同。
    这也解释了为什么String类中,
    如果两个对象的equals方法相同,则他们的hashcode值一定相同。
    3、对于两个对象来说,如果使用equals方法返回为false,则他们的hashcode的值有可能相等也可能不等,
    (如果不同会提高性能,因为在集合中类判断两个对象是否相等,如果其hashcode不等就直接不用判断equals方法了)

    4、对于Object对象来说,不同的Object对象的hashcode是不同的,它们返回的是对象的地址,
    equals返回的也是对象的地址。所以在自己定义的类中如果要添加到集合对象中,

    最好是要重写hashcode和equals方法,不然会自动继承自Object类中的两个方法根据对象地址来判断。
    在重写自己定义的类时,通常是在类中的根据某个值如name.hashcode();来进行判断。


    三、以HashSet 为例:
    当我们使用HashSet时,hashCode()方法就会被得到调用,判断已经存储在集合中的对象的hashCode值是否与所增加。
    对象的hashCode值一致,如果“不一致”则直接加进去(不用比较equals()提高效率),如果一致,则进行equals方法的比较,如果返回true,表明集合里面已经有这个对象,不能添加进去了。如果是false表是集合里面没有这个对象,则可以加进去。所以在重写hashcode()或者equals() 方法的任何一个方法时,必须重写另外一个。
    示例代码:
    /**
    * People 手工重写hashcode方法和equals方法 根据name来判断 两个对象是否相等。
    */
    class People {
    private String name;
    public People(String name) {
    this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
    //如果是自己
    if(this==obj){
    return true ;
    }
    //如果是空
    if(obj==null ){
    return false;
    }
    //比较两个People的名字是否相同
    if(obj!=null && obj instanceof People){
    if(((People)obj).name.equals(this.name))
    return true ;
    }
    return false;
    }
    @Override
    public int hashCode() {
    // String的hashcode本来就是用来比较两个字符是否相等
    return name.hashCode();
    }
    }

    从Object类的hashCode()和equals()方法讲起:   

    
    

          最近看了Object类的源码,对hashCode() 和equals()方法有了更深的认识。重写equals()方法就必须重写hashCode()方法的原因,从源头Object类讲起就更好理解了。

    
    

    先来看Object关于hashCode()和equals()的源码:

    
    
    
    
    
    [java] view plain copy
     
    1. public native int hashCode();  
    
    
    [java] view plain copy
     
    1. public boolean equals(Object obj) {  
    2.     return (this == obj);  
    3. }  
         光从代码中我们可以知道,hashCode()方法是一个本地native方法,返回的是对象引用中存储的对象的内存地址,而equals方法是利用==来比较的也是对象的内存地址。从上边我们可以看出,hashCode方法和equals方法是一致的。还有最关键的一点,我们来看Object类中关于hashCode()方法的注释:
    
    
    
    
    
    [java] view plain copy
     
    1. /** 
    2.      * Returns a hash code value for the object. This method is 
    3.      * supported for the benefit of hash tables such as those provided by 
    4.      * {@link java.util.HashMap}. 
    5.      * <p> 
    6.      * The general contract of {@code hashCode} is: 
    7.      * <ul> 
    8.      * <li>Whenever it is invoked on the same object more than once during 
    9.      *     an execution of a Java application, the {@code hashCode} method 
    10.      *     must consistently return the same integer, provided no information 
    11.      *     used in {@code equals} comparisons on the object is modified. 
    12.      *     This integer need not remain consistent from one execution of an 
    13.      *     application to another execution of the same application. 
    14.      * <li>If two objects are equal according to the {@code equals(Object)} 
    15.      *     method, then calling the {@code hashCode} method on each of 
    16.      *     the two objects must produce the same integer result. 
    17.      * <li>It is <em>not</em> required that if two objects are unequal 
    18.      *     according to the {@link java.lang.Object#equals(java.lang.Object)} 
    19.      *     method, then calling the {@code hashCode} method on each of the 
    20.      *     two objects must produce distinct integer results.  However, the 
    21.      *     programmer should be aware that producing distinct integer results 
    22.      *     for unequal objects may improve the performance of hash tables. 
    23.      * </ul> 
    24.      * <p> 
    25.      * As much as is reasonably practical, the hashCode method defined by 
    26.      * class {@code Object} does return distinct integers for distinct 
    27.      * objects. (This is typically implemented by converting the internal 
    28.      * address of the object into an integer, but this implementation 
    29.      * technique is not required by the 
    30.      * Java™ programming language.) 
    31.      * 
    32.      * @return  a hash code value for this object. 
    33.      * @see     java.lang.Object#equals(java.lang.Object) 
    34.      * @see     java.lang.System#identityHashCode 
    35.      */  
    36.     public native int hashCode();  
        简单的翻译一下就是,hashCode方法一般的规定是:
    
    
    
    
    
    [java] view plain copy
     
    1. 1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。      
    2. 2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。      
    3. 3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。  
    
    
    
    
    

         再简单的翻译一下第二三点就是:hashCode()和equals()保持一致,如果equals方法返回true,那么两个对象的hasCode()返回值必须一样。如果equals方法返回false,hashcode可以不一样,但是这样不利于哈希表的性能,一般我们也不要这样做。重写equals()方法就必须重写hashCode()方法的原因也就显而易见了。

    
    

       假设两个对象,重写了其equals方法,其相等条件是属性相等,就返回true。如果不重写hashcode方法,其返回的依然是两个对象的内存地址值,必然不相等。这就出现了equals方法相等,但是hashcode不相等的情况。这不符合hashcode的规则。下边,会介绍在集合框架中,这种情况会导致的严重问题。

    
    

        重写的作用:

    
    
            如果重写(用于需求,比如建立一个Person类,比较相等我只比较其属性身份证相等就可不管其他属性,这时候重写)equals,就得重写hashCode,和其对象相等保持一致。如果不重写,那么调用的Object中的方法一定保持一致。
    
    
           1.  重写equals()方法就必须重写hashCode()方法主要是针对HashSet和Map集合类型。集合框架只能存入对象(对象的引用(基本类型数据:自动装箱))。
    
    
            在向HashSet集合中存入一个元素时,HashSet会调用该对象(存入对象)的hashCode()方法来得到该对象的hashCode()值,然后根据该hashCode值决定该对象在HashSet中存储的位置。简单的说:HashSet集合判断两个元素相等的标准是:两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值也相等。如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不同,HashSet会把它们存储在不同的位置,依然可以添加成功。同样:在Map集合中,例如其子类Hashtable(jdk1.0错误的命名规范),HashMap,存储的数据是<key,value>对,key,value都是对象,被封装在Map.Entry,即:每个集合元素都是Map.Entry对象。在Map集合中,判断key相等标准也是:两个key通过equals()方法比较返回true,两个key的hashCode的值也必须相等。判断valude是否相等equal()相等即可。
    
    
          稍微提一句:(1)两个对象,用==比较比较的是地址,需采用equals方法(可根据需求重写)比较。
    
    
                                  (2)重写equals()方法就重写hashCode()方法。
    
    
                                   (3)一般相等的对象都规定有相同的hashCode。
    
    
                                    hash:散列,Map关联数组,字典
    
    
         2. 集合类都重写了toString方法。String类重写了equal和hashCode方法,比较的是值。
    
    

         用HashSet来验证两个需都重写的必要性

    
    
          程序提供了三个类A,B,C,它们分别重写了equals(),hashCode()两个方法中的一个或全部。

    class A {
    String certificate;

    public A(String certificate) {

    this.certificate = certificate;
    }

    @Override
    public boolean equals(Object obj) {
    if (this == obj)
    return true;
    if (obj == null)
    return false;
    if (getClass() != obj.getClass())
    return false;
    A other = (A) obj;
    if (certificate == null) {
    if (other.certificate != null)
    return false;
    } else if (!certificate.equals(other.certificate))
    return false;
    return true;
    }

    }

    class B {
    String certificate;// 身份证号

    public B(String certificate) {

    this.certificate = certificate;
    }

    @Override
    public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((certificate == null) ? 0 : certificate.hashCode());
    return result;
    }
    }

    class C {
    String certificate;

    public C(String certificate) {

    this.certificate = certificate;
    }

    @Override
    public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((certificate == null) ? 0 : certificate.hashCode());
    return result;
    }

    @Override
    public boolean equals(Object obj) {
    if (this == obj)
    return true;
    if (obj == null)
    return false;
    if (getClass() != obj.getClass())
    return false;
    C other = (C) obj;
    if (certificate == null) {
    if (other.certificate != null)
    return false;
    } else if (!certificate.equals(other.certificate))
    return false;
    return true;
    }

    }

    
    
     
     

    package day02;

    import java.util.HashSet;

    public class EqualsHashcodeDemo {
    public static void main(String[] args) {

    HashSet<A> hashSetA = new HashSet();
    hashSetA.add(new A("123"));
    hashSetA.add(new A("123"));
    for (Object hs : hashSetA) { 
    System.out.println(hs); 



    HashSet<B> hashSetB = new HashSet<B>();
    hashSetB.add(new B("345"));
    hashSetB.add(new B("345"));
    for (Object hs : hashSetB) { 
    System.out.println(hs); 
    }
    HashSet<C> hashSetC = new HashSet<C>();
    hashSetC.add(new C("456"));
    hashSetC.add(new C("456"));
    for (Object hs : hashSetC) { 
    System.out.println(hs); 
    }
    /**
    * 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; }
    */

    }
    }

    其结果为:
    
    
    
    
    
    
    [java] view plain copy
     

    cn.edu.uestc.collection.B@1  

    day02.A@15db9742

    day02.A@6d06d69c  //第一个set(hashSetA)中竟然把同一个元素放入了set中,两次,且算出的hashcode值也不一致,这就说明将同一元素放到了同一个set的不同位置,里面有两个,一个set中的不同位置放了两个相同的A;

    day02.B@c613

    day02.B@c613    //第二个set(hashSetB)也把同一个元素放入了set中,两次,算出的hashcode值一致,这就说明将同一元素放到了同一个set的相同位置,放了两次而已, 同一个set的相同位置放了两个相同的B;

    day02.C@c9f4  //第三个set(hashSetC)也把同一个元素放入了set中,两次,但是set中只保存了一个C,说明只有重写两个方法,才能达到结果;

        从上边的程序结果可以看到,必须要同时重写这两个方法,要不然Set的特性就被破坏了。
     
     
    
    

         重写hashCode()的原则

    
    
        (1)同一个对象多次调用hashCode()方法应该返回相同的值;
    
    
        (2)当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()应该返回相等的(int)值;
    
    
        (3)对象中用作equals()方法比较标准的Filed(成员变量(类属性)),都应该用来计算hashCode值。
    
    
          计算hashCode值的方法:      
    
    
    [java] view plain copy
     
    1. //f是Filed属性  
    2. boolean    hashCode=(f?0:1)  
    3. (byte,short,char,int)      hashCode=(int)f  
    4. long       hashCode=(int)(f^(f>>>32))  
    5. float       hashCode=Float.floatToIntBits(f)  
    6. double   hashCode=(int)(1^(1>>>32))  
    7. 普通引用类型    hashCode=f.hashCode()  
           将计算出的每个Filed的hashCode值相加返回,为了避免直接相加产生的偶然相等(单个不相等,加起来就相等了),为每个Filed乘以一个质数后再相加,例如有:
    
    
    [java] view plain copy
     
    1. return  f1.hashCode()*17+(int)f2.13  
         
    
    
        查看String源码,看hashCode()d的实现方法:
    
    
     
    
    
     
    
    
    [java] view plain copy
     
    1. /** 
    2.    * Returns a hash code for this string. The hash code for a 
    3.    * <code>String</code> object is computed as 
    4.    * <blockquote><pre> 
    5.    * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 
    6.    * </pre></blockquote> 
    7.    * using <code>int</code> arithmetic, where <code>s[i]</code> is the 
    8.    * <i>i</i>th character of the string, <code>n</code> is the length of 
    9.    * the string, and <code>^</code> indicates exponentiation. 
    10.    * (The hash value of the empty string is zero.) 
    11.    * 
    12.    * @return  a hash code value for this object. 
    13.    */  
    14.   public int hashCode() {  
    15.       int h = hash;  
    16.       if (h == 0 && value.length > 0) {  
    17.           char val[] = value;  
    18.   
    19.           for (int i = 0; i < value.length; i++) {  
    20.               h = 31 * h + val[i];  
    21.           }  
    22.           hash = h;  
    23.       }  
    24.       return h;  
    25.   }  
     
    [java] view plain copy
     
    1. public class A {  
    2.     @Override  
    3.     public boolean equals(Object obj) {  
    4.         return true;  
    5.     }  
    6. }  

    [java] view plain copy
     
    1. public class B {  
    2.     @Override  
    3.     public int hashCode() {  
    4.         return 1;  
    5.     }  
    6. }  

    [java] view plain copy
     
    1. public class C {  
    2.     @Override  
    3.     public int hashCode() {  
    4.         return 2;  
    5.     }  
    6.   
    7.     @Override  
    8.     public boolean equals(Object obj) {  
    9.         return true;  
    10.     }  
    11. }  
    
    
    [java] view plain copy
    1. public class HashSetTest {  
    2.     public static void main(String[] args) {  
    3.         HashSet hashSet = new HashSet();  
    4.         hashSet.add(new A());  
    5.         hashSet.add(new A());  
    6.         hashSet.add(new B());  
    7.         hashSet.add(new B());  
    8.         hashSet.add(new C());  
    9.         hashSet.add(new C());  
    10.         for (Object hs : hashSet) {  
    11.             System.out.println(hs);  
    12.         }  
    13.         //HashSet重写了toString()方法  
    14. //        System.out.println(hashSet);  
    15.     }  
    16. }  
         其结果为:
    
    
    
    
    
    
    [java] view plain copy
     
    1. cn.edu.uestc.collection.B@1  
    2. cn.edu.uestc.collection.B@1  
    3. cn.edu.uestc.collection.C@2  
    4. cn.edu.uestc.collection.A@3f84246a  
    5. cn.edu.uestc.collection.A@18a9fa9c  
    6. Process finished with exit code 0  
        从上边的程序结果可以看到,必须要同时重写这两个方法,要不然Set的特性就被破坏了。
     
  • 相关阅读:
    Zookeeper环境搭建
    Zookeeper介绍
    相关错题
    数据库前三章测试题
    数据库相关练习题
    SQL语句操作数据
    用表组织数据
    创建和管理SQL Server数据库
    登陆数据库
    C#部分试题实例
  • 原文地址:https://www.cnblogs.com/erma0-007/p/8607634.html
Copyright © 2011-2022 走看看