关于 equals() 与 hashCode() 个人理解总结

1:如果我们自定义的类没有重写 hashCode 和 equals() 方法的话 ?
在使用 equals 对比的时候,是错误的!

因为没有重写的类都是继承祖宗 Object 的  hashCode  和 equals 方法 , 这是Object 源码的 hashCode 和 equals 方法

    public native int hashCode();

    public boolean equals(Object obj) {
        return (this == obj);
    }

hashCode 方法有  native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C== 语言实现的,由java去调用.

(对于还不理解hashCode 的人来说,先断言  Object 类 hashCode 返回的就是对象的存储地址,有助于理解)

equals 方法直接对比的是  对象的内存地址 , 跟内容毫无关系!!! 

实战案例: 

package com.example;

import lombok.Data;
import org.junit.Test;

import java.util.*;

/**
 * @Author: qiuj
 * @Description:
 * @Date: 2019年8月6日17:22:06
 */
public class HashCodeTest {

    @Test
    public void test4(){
        HashSet hashSet = new HashSet();
        hashSet.add(new Handsome(18,"邱健"));
        hashSet.add(new Handsome(18,"邱健"));
        Iterator iterator = hashSet.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }

    class Handsome{
        int age;
        String name;

        public Handsome(int age,String name){
            this.age = age;
            this.name = name;
        }

        public String toString(){
            return age + ":" +name;
        }

    }
}

我们用  Set 集合,集合内的元素不重复。结果返回false  。就是因为 Handsome 类没有重写 hashCode 和 equals 方法

set 在添加第二个对象时,先把 hashCode 算出来 ,发现容器内并没有此 hashCode 的值。直接就放入容器内了。此时都还没有进行 equals 的对比 。 

深入 hashCode 底层

实际上:hashCode 不一定都是返回对象的存储地址,但是有些JVM在实现时是直接返回对象的存储地址,但是大多时候并不是这样,只能说可能存储地址有一定关联

下面是HotSpot JVM中生成hash散列值的实现:

static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // This form uses an unguarded global Park-Miller RNG,
     // so it's possible for two threads to race and generate the same RNG.
     // On MP system we'll have lots of RW access to a global, so the
     // mechanism induces lots of coherency traffic.
     value = os::random() ;
  } else
  if (hashCode == 1) {
     // This variation has the property of being stable (idempotent)
     // between STW operations.  This can be useful in some of the 1-0
     // synchronization schemes.
     intptr_t addrBits = intptr_t(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else
  if (hashCode == 2) {
     value = 1 ;            // for sensitivity testing
  } else
  if (hashCode == 3) {
     value = ++GVars.hcSequence ;
  } else
  if (hashCode == 4) {
     value = intptr_t(obj) ;
  } else {
     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  }
 
  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}

  该实现位于hotspot/src/share/vm/runtime/synchronizer.cpp文件下。

2:hashCode 的作用?

hashCode 的作用在于 效率 二字,如上个案例,当我们放入第二个重复对象的时候。他先会去比较hashCode 值,如果不一致 ,直接说明两个内容不一致。放入容器内。如果 hashCode 一致,再去执行equals 方法 进行 一个一个 value 的对比。如果此时容器内有 1000 个 ,这时在添加1个元素 。 没有hashCode  那不是要对比  1000次 ,而且每次都一个一个 value 的对比。那效率可想而知!!!

案例1:我们可以看看 String hashCode()的源码实现。  可以看出hashCode 的结果是固定的,并不是随机的。只要 值一致就会产生一样的结果。所以 以此说明如果 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;
    }

案例2:String equals()的源码实现,可以看出先比较引用,然后就是完全的比较  每个字符了。这样保证内容都是一致的

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

3:两个对象  hashCode 一致,他们完全相同吗?

不一定,以 String 来说, hashCode 一致那么内容肯定是一致了。 equals 方法肯定也是 true 。但是他们附属在的对象内存不一致

假例:第一个 hashCode =1018547642  内存地址=@1018547642  那第二个 hashCode 也是一样。内存地址也一样,但是该地址已经存放数据了, 就需要再次找到个没人占用的内存地址进行存储 。 不相同指出就在于  内存地址

3:为什么重写了 equals()  ,还要重写 hashCode()

(1):自定义对象没有重写  hashCode  ,那么放入  Set 集合类型的容器就会导致重复的元素放入到容器内。

因为 Set 类型集合的执行顺序:

                                                 会先判断 hashCode  如果连  hashCode 都不一致 

                                                 那就不会执行 equals 方法了

这也牵扯出另一个问题 , 就是效率 。 如果没有重写 hashCode ,每次都去 执行 equals 方法和容器内的所有元素统统对比一次。那程序的效率将导致非常低下!!!

如有错误,恳请指出!

posted @ 2019-08-07 10:53  邱健  阅读(82)  评论(0编辑  收藏  举报