为什么重写equals()就尽量要重写hashCode()方法?

以下为本文目录:

首先我们分别解释equals()方法和hashCode()方法分别是用来做什么的?
我们再来看equals()方法和hashCode()方法两者有什么关系?
最后来看为什么重写equals()就一定要重写hashCode()方法?
1、首先我们分别解释equals()方法和hashCode()方法分别是用来做什么的?
equals()方法:
很明显,该方法就是用来判断两个对象是否是同一个对象。在Object类源码(如下所示)中,其底层是使用了“==”来实现,也就是说通过比较两个对象的内存地址是否相同判断是否是同一个对象。

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

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

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

public native int hashCode();

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

2、我们再来看equals()方法和hashCode()方法两者有什么关系?
两者事实关系如下: 
1. 如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同!!!!; 
2. 如果两个对象不同(即用equals比较返回true),那么它们的hashCode值可能相同也可能不同; 
3. 如果两个对象的hashCode相同(存在哈希冲突),那么它们可能相同也可能不同(即equals比较可能是false也可能是true) 

4. 如果两个对象的hashCode不同,那么他们肯定不同(即用equals比较返回false)

两个对象用equals方法比较为true,它们的Hashcode值相同吗?

       答:不一定相同。正常情况下,因为equals()方法比较的就是对象在内存中的值,如果值相同,那么Hashcode值也应该相同。但是如果不重写hashcode方法,就会出现不相等(默认比较的是内存中的地址值)的情况。

 

3、最后来看为什么重写equals()就一定要重写hashCode()方法?
对于对象集合的判重,如果一个集合含有10000个对象实例,仅仅使用equals()方法的话,那么对于一个对象判重就需要比较10000次,随着集合规模的增大,时间开销是很大的。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且在定位到大概存储位置后,后续比较过程中,如果两个对象的hashCode不相同,也不再需要调用equals()方法,从而大大减少了equals()比较次数。 
所以从程序实现原理上来讲的话,既需要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()方法,会出现什么情况? 

public class Customer{
    private String name;

    private int age;

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj){
            return true;
        }
        if (!(obj instanceof Customer)){
            return false;
        }
        final Customer other = (Customer) obj;
        if (this.name.equals(other.getName()) && this.age == other.getAge()){
            return true;
        } else{
            return false;
        }
    }

//    @Override
//    public int hashCode() {
//        int result;
//        result = (name == null ? 0 : name.hashCode());
//        result = 29 * result + age;
//        return result;
//    }

    public static void main(String[] args) {
        Set<Customer> set = new HashSet<Customer>();
        Customer customer1 = new Customer("Tom", 15);
        Customer customer2 = new Customer("Tom", 15);
        set.add(customer1);
        set.add(customer2);
        System.err.println(set.toString());
        HashMap map=new HashMap(4);
        map.put(customer1,"customer1");
        map.put(customer2,"customer2");
        System.out.println(map.toString());
    }
}

输出:  {Customer{name='Tom', age=15}=customer1, Customer{name='Tom', age=15}=customer2}
          [Customer{name='Tom', age=15}, Customer{name='Tom', age=15}]

字段属性值完全相同的两个对象因为hashCode不同,所以在hashmap中的table数组的下标不同,从而这两个对象就会同时存在于集合中,所以重写equals()就一定要重写hashCode()方法。
同理 如果仅仅重写了hashCode(),而没有重写equals()方法 在hashmap中的table数组的下标相同  但是没有重写equals()方法导致认为不是一个key不会覆盖,将会形成一个链表

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

--------------------- 
作者:Nostark 
来源:优快云 
原文:https://blog.youkuaiyun.com/sixingmiyi39473/article/details/78306296
版权声明:本文为博主原创文章,转载请附上博文链接!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值