浅析hashcode和equals

 前言:   hashCode是jdk根据对象的地址  或者  字符串或者数字  算出来的int类型的数值。

1.  jdk源代码 ——不同类中的hashcode方法

  1:Object类的hashCode().返回对象的内存地址,由于每个对象的内存地址都不一样,所以哈希码也不一样。

    

 public native int hashCode();
这就是Object.java中的hashcode源代码
分析:1.使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。
    这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的,这也是java的底层机制。
    实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。 
   2.native的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。所以native关键字的函数都是操作系统实现的, java只能调用。

  2:String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同。

String.java 类中的hashcode源代码
 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;
    }
分析:

String类中的hashCode计算方法还是比较简单的,就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模。

哈希计算公式可以计为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

关于为什么取31为权?主要是因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。

 String s1 = new String("2");
        String s2 = "2";
        String s3 = "A";
        String s4 = "a";
        System.out.println(s1.hashCode());   //50
        System.out.println(s2.hashCode());   //50
        System.out.println(s3.hashCode()+" "+s4.hashCode());   //65   97

  3:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值。

 @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }
Integer i2 = new Integer(2);
Integer i3 = new Integer(2);
System.out.println(i2.hashCode()+" "+i3.hashCode());  // 2   2

2.  jdk源代码 ——不同类中的equals方法

Java语言对equals()的要求如下,这些要求是必须遵循的:

  A 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

  B 反射性:x.equals(x)必须返回是“true”。

  C 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

  D 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

  任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”

  

  1 .  Object.java 类中的equals方法 ,  默认比较的是对象的地址,因为只有是相同的地址才会相等.

1 //lang包--   Object.java  中的equals();
2     public boolean equals(Object obj) {
3         return (this == obj);
4     }

  2 .  String.java类中的equals方法 , 比较的是对象的值。   

判断条件分为四步:

          1.  若当前对象和比较的对象是同一个对象,即return true。也就是Object中的equals方法。

     2.  若当前传入的对象是String类型,则比较两个字符串的长度,即value.length的长度。 若长度不相同,则return false

          3.  若长度相同,则按照数组value中的每一位进行比较,不同,则返回false。若每一位都相同,则返回true。

          4.  若当前传入的对象不是String类型,则直接返回false

public boolean equals(Object anObject)
{
       //如果是同一个对象
        if (this == anObject)
        {
            return true;
        }
        //如果传递进来的参数是String类的实例
        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;
}

  3.   Integer类中的equals方法 ,  首先判断比较的对象是不是Integer类型的,若是,则转为int基本类型,再用 == 比较,比较的就是数值了

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

Integer类中的intValue方法

public int intValue() {
   return value;
}

 

3. 先提出个问题: 当向 HashSet 集合 (无须,不重复) 中插入对象时,如何判别在集合中是否已经存在该对象了?

    分析: 1. 如果先用 equals方法来逐个进行比较,倘若集合中已经存在一万条数据或者更多的数据,效率必然是一个问题

      2. 此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不插入,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了。

说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值

下面这段代码是java.util.HashMap的中put方法的具体实现:

public V put(K key, V value) {
        //当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因
        if (key == null)
            return putForNullKey(value);
        //计算key的hash值
        int hash = hash(key.hashCode());                  ------(1)
        //计算key hash 值在 table 数组中的位置
        int i = indexFor(hash, table.length);             ------(2)
        //从i出开始迭代 e,找到 key 保存的位置
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            //判断该条链上是否有hash值相同的(key相同)
            //若存在相同,则直接覆盖value,返回旧value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;    //旧值 = 新值
                e.value = value;
                e.recordAccess(this);
                return oldValue;     //返回旧值
            }
        }
        //修改次数增加1
        modCount++;
        //将key、value添加至i位置处
        addEntry(hash, key, value, i);
        return null;
    }

    put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

总结:

HashSet判断两个对象是否相等 必须同时满足两个条件:

  1.    hashcode是否相等
  2.  equals是否相等

4. 几种情况比较

   1.  只看java类库中的各个方法,没有自定义重写的情况下。

    两个对象==相等,则其hashcode一定相等,反之不一定成立。

    两个对象equals相等,则其hashcode一定相等,反之不一定成立。?【 因为Object的equals实现用的就是 对象的==相等来判断】

     String s1 = new String("2");
        String s2 = new String("2");
        String s3 = "4";
        String s4 = "4";
        
        System.out.println(s1.hashCode()==s2.hashCode());   //true
        System.out.println(s1.equals(s2));  //true      //Stirng类中重写了equals方法
        System.out.println(s1==s2);         //false    //两个对象的hashcode 相同,== 不一定相同。
        
        System.out.println(s3.hashCode()==s4.hashCode());   //true
        System.out.println(s3.equals(s4));  //true
        System.out.println(s3==s4);        //true

5. 为什么重写equals时 总是要改写hashcode ?

java.lnag.Object中对hashCode的约定:
   1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
   2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
   3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

根据上一个问题,实际上我们已经能很简单的解释这一点了,比如改写String中的equals为基于内容上的比较而不是内存地址的话,那么虽然equals相等,但并不代表内存地址相等,由hashcode方法的定义可知内存地址不同,没改写的hashcode值也可能不同。所以违背了第二条约定。

又如new一个对象,再new一个内容相等的对象,调用equals方法返回的true,但他们的hashcode值不同,将两个对象存入HashSet中,会使得其中包含两个相等的对象,因为是先检索hashcode值,不等的情况下才会去比较equals方法的。

6.根据实际需求重写equals 方法和 hashcode 方法(常见的写法如下)

        
        @Override
        public boolean equals(Object obj){
            if(obj == null){
                return false;
            }
            if(this == obj){
                return false;
            }
            
            if(obj instanceof Stduent){
                Stduent ss = (Stduent)obj;
                if(this.id==ss.id&&this.name.equals(ss.name)){
                    return true;
                }
            }
            return false;
        }
        
        @Override
        public int hashCode() {
            //return this.id+this.name.hashCode();//返回的是跟塑所有属性值 相关 的int值
            //上面这种写法产生的问题是不同的属性值相加之后  可能会存在相同的结果
            //解决办法: 将每个属性值用字符串进行拼接,不会去运算,也就不会产生上述问题
            String s =   ""  +  this.id+this.name.hashCode();
            return Integer.parseInt(s);
        }

 

原文地址:https://www.cnblogs.com/gshao/p/10127667.html