【Java基础】分析重写equals()方法就必须重写hashCode()方法的原因

equals()方法和hashCode()方法是用来做什么的?

equals()方法

  • 该方法就是用来判断两个对象是否是同一个对象。在Object类源码(如下所示)中,其底层是使用了“==”来实现,也就是说通过比较两个对象的内存地址是否相同判断是否是同一个对象。
public boolean equals(Object obj) {
	return (this == obj);
}

但是在实际开发中,该方法不能满足的我们的需求。因为我们认为两个对象即使不是指向的同一块内存,只要这两个对象的各个字段属性值都相同,那么就认为这两个对象是同一个对象。所以就需要重写equals()方法即如果两个对象指向内存地址相同或者两个对象各个字段值相同,那么就是同一个对象。

当然,其他的很多类继承Object后,会对equals()进行重写,它会对两个对象的类型和对象字段值进行比较,这个equals()对字符串相同的对象会得到true。可以看下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;
}

hashCode()方法

  • 一提到hashcode,很自然就想到哈希表Hashtable。将某一key值映射到表中的一个位置,从而达到以O(1)的时间复杂度来查询该key值。Object类源码(如下所示)中,hashCode()是一个native方法,哈希值的计算利用的是内存地址。
public native int hashCode();

可以认为利用哈希表也能起到一定的判重的作用,但是现实是可能存在哈希冲突即使是两个不同的对象,他们的哈希值也可能相同,如何解决哈希冲突,这里就不讲了。总之,我们记住哈希表具有优越的查询性能,并且存在哈希冲突。

两个内容相同的对象的哈希值也会是不同的,所以重写hashCode()也是根据需求来重写,比如,String类(如下所示)的hashCode()的功能,对内容相同的字符串变量,会返回相同的哈希值。如果是调用Object的hashCode()方法,因为两个String变量是两个引用对象,返回的哈希值可能会不相等的。(如果String a = “1” 和 String b = new String("1") 比较肯定不相等)

public int hashCode() {
    int h = hash;//默认hash=0
    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;
}

可以看出,它是对两个字符串按照下面的规则,计算新的哈希值,这样算出的来两个相同字符串相变量哈希值是相等的。

h = 31 * h + val[i];

equals()方法和hashCode()方法有什么关系?

  1. 如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同!
  2. 如果两个对象不同(即用equals比较返回false),那么它们的hashCode值可能相同也可能不同;
  3. 如果两个对象的hashCode相同(存在哈希冲突),那么它们可能相同也可能不同(即equals比较可能是false也可能是true)
  4. 如果两个对象的hashCode不同,那么他们肯定不同(即用equals比较返回false)

为什么重写equals()就一定要重写hashCode()方法?

  1. 对于对象集合的判重,如果一个集合含有10000个对象实例,仅仅使用equals()方法的话,那么对于一个对象判重就需要比较10000次,随着集合规模的增大,时间开销是很大的。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的hashCode不相同,也不再需要调用equals()方法,从而大大减少了equals()比较次数。
  2. 所以从程序实现原理上来讲的话,既需要equals()方法,也需要hashCode()方法。那么既然重写了equals(),那么也要重写hashCode()方法,以保证两者之间的配合关系。

基于以上分析,我们可以在Java集合框架中得到验证。由于HashSet是基于HashMap来实现的,所以这里只看HashMap的put方法即可。源码如下

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        //这里通过哈希值定位到对象的大概存储位置
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //if语句中,先比较hashcode,再调用equals()比较
            //由于“&&”具有短路的功能,只要hashcode不同,也无需再调用equals方法
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

由源码注释,可以得到验证,我们在实际应用过程中,如果仅仅重写了equals(),而没有重写hashCode()方法,会出现什么情况?

  • 字段属性值完全相同的两个对象因为hashCode不同,所以在Hashmap中的table数组下标不同从而这两个对象就会同时存在于集合中,所以重写equals()就一定要重写hashCode()方法。

对于“为什么重写equals()就一定要重写hashCode()方法”

  • 这个问题应该是有个前提,就是你需要用到HashMap,HashSet等Java集合。如果用不到哈希表的话,其实仅仅重写equals()方法也可以吧。而工作中的场景是常常用到Java集合,所以Java官方建议 重写equals()就一定要重写hashCode()方法。

重写的作用

(比如建立一个Person类,如果两个对象的身份证字段值相等就可以 人为这两个对象是同一个对象) 如果重写equals,就得重写hashCode,和其对象相等保持一致。如果不重写,那么调用的Object中的方法一定保持一致。

扫描二维码关注公众号,回复: 8712924 查看本文章
  • 重写equals()方法就必须重写hashCode()方法,主要是针对HashSet和Map集合类型集合框架只能存入对象(对象的引用,如果是基本类型数据:自动装箱)。
  1. 在向HashSet集合中存入一个元素时,HashSet会调用该对象(存入对象)的hashCode()方法来得到该对象的hashCode()值,然后根据该hashCode值决定该对象在HashSet中存储的位置。

  2. 简单的说:HashSet集合判断两个元素相等的标准是:两个对象通过equals()方法比较相等,并且两个对象的HashCode()方法返回值相等。如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不同,HashSet会把它们存储在不同的位置,依然可以添加成功。

  3. 同样:在Map集合中,例如其子类Hashtable(jdk1.0错误的命名规范)HashMap,存储的数据是键值对,key,value都是对象,被封装在Map.Entry里(即:集合里的每个键值对都是Map.Entry对象)在Map集合中,判断key相等标准也是:两个key通过equals()方法比较返回true,两个key的hashCode的值也必须相等。判断value是否相等equal()相等即可。

  4. 总结:
    (1)两个对象,用==比较比较的是内存地址,需采用equals方法(根据需求重写)比较。
    (2)重写equals()方法就重写hashCode()方法。
    (3)一般相等的对象都规定有相同的hashCode。如: hash:散列,Map关联数组,字典

  5. 集合类都重写了toString方法。String类重写了equal和hashCode方法,比较的是值。

重写hashCode()的原则

  1. 同一个对象多次调用hashCode()方法应该返回相同的值;
  2. 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()应该返回相等的(int)值;
  3. 对象中用作equals()方法比较的Filed(成员变量(类属性)),都应该用来计算hashCode值。

计算hashCode值的方法

//f是Filed属性
boolean    				   hashCode=(f?0:1)
(byte,short,char,int)      hashCode=(int)f
long       			       hashCode=(int)(f^(f>>>32))
float       			   hashCode=Float.floatToIntBits(f)
double                     hashCode=(int)(1^(1>>>32))
普通引用类型                 hashCode=f.hashCode()

将计算出的每个Filed的hashCode值相加返回,为了避免直接相加产生的偶然相等(单个不相等,加起来就相等了),为每个Filed乘以一个质数后再相加,例如有:

return  f1.hashCode() * 17 + (int)f2.13

总结

重写equals()方法后需不不要重写hashCode()方法,主要原因在于,当使用一个对象作为HashMap,HashSet等Java集合的key时,key值的计算问题。当我们用自定义的类的作为key时,怎么判断这相同的类对象的唯一性,这里涉及到HashMap的判断机制,先比较hashCode(),当哈希值相同时,再比较equals(),这两个是配套的。
在这里插入图片描述

发布了62 篇原创文章 · 获赞 109 · 访问量 5299

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/102998314