重写equals的同时为啥要重写hashCode?

HashMap引申的问题

HashMap在重写equal一定要重写hashCode

Effective java这本书中,存在这么一句话,**在每个覆盖了equals方法的类中,都必须覆盖hashCode方法。**大部分人应该都知道这个规则,但为什么要这样做,以及不这么做会出现这么问题呢?接下来,将对以上问题做探讨。

下面有一个重写了equals方法的PhoneNum类:

class PhoneNum{
    private final int a,b,c;

    PhoneNum(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    public boolean equals(Object o){
        if(o==this){
            return true;
        }
        if(! (o instanceof PhoneNum) ){
            return false;
        }
        PhoneNum pn=(PhoneNum) o;
        return pn.a==a&&pn.b==b&&pn.c==c;
    }
}


注意:此类并没有重写hashCode方法。

接下来,我把这个类的实例put进hashMap

class Test{
    static HashMap hashMap=new HashMap();

    public static void main(String[] args) {

        hashMap.put(new PhoneNum(1,1,1),"obj1");

        Object value=hashMap.get(new PhoneNum(1,1,1)); //获取值
        System.out.println(value);
    }
}

猜一猜,程序会输出什么?为obj1吗?

下面来看看输出结果:

在这里插入图片描述

接下来我再在PhoneNum类中重写hashCode方法:

class PhoneNum{
    private final int a,b,c;

    PhoneNum(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    public boolean equals(Object o){
        if(o==this){
            return true;
        }
        if(! (o instanceof PhoneNum) ){
            return false;
        }
        PhoneNum pn=(PhoneNum) o;
        return pn.a==a&&pn.b==b&&pn.c==c;
    }
    public int hashCode(){
        return 42;
    }

}

再运行Test中main方法,得到的输出:

在这里插入图片描述

是不是想问一句为啥呢?hashCode有这么强大吗

首先,我们先看看hashCode到底是啥,有啥作用

我们先找找Object中的hashCode方法,是不是想问为啥要在Object中找,记住,Java中的所有类都继承自Object,是不是又要问没有用extends关键字,是怎么继承的,这个以后再说,先记住这点。

public native int hashCode();

oops!!原来是本地方法,而且实现体不是用java实现的

再来看看官方文档对hashCode的描述:

hashCode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。 

hashCode 的常规协定是: 
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。 
以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。 
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。) 

当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

那么,对象的hashCode到底是怎么来的呢

通过对象的内部地址(也就是物理地址)转换成一个整数,然后该整数通过hash函数的算法就得到了hashCode。那么hashCode有什么用呢?HashCode是用来在散列存储结构中确定对象的存储地址的。换句话说,hashCode是用来对象定位其在hash表中位置的。HashCode的作用主要是为了查找的快捷性。比如,在查找对象时,根据对象的hashCode就快速定位对象所在hash表的位置,但此时要注意一点,也就是hash冲突,hash冲突也就是不同对象的hashCode一样,这时候在hash表中存放的位置也一样,这时候就可以用链表的形式把这些hashCode相同的对象串起来,这也就是解决hash冲突最常用的方法——拉链法,在根据hashCode查到具体位置后,将要查找的对象与对应位置的所有对象做比对,直到查到对象。

这下明白了hashCode的作用,那么hashCode与equals又有什么关系呢,先来看看equals方法吧

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

equals方法采用的是地址比较,至少说是跟hashCode毫无关系。

但是为什么官方文档会强调重写了equals,就一定要重写hashCode,我们先回忆一下,HashMap,HashSet以及HashTable,它们在添加元素的时候,是根据什么来确定存放位置的。这里以HashMap为例子,HashMap在确定元素在Node数组中的位置时,在hash函数中,用hashCode的高16位与低16位做了异或运算。可见,HashMap在确定位置时用到了hashCode。

现在,我们再回到之前的代码,hashMap.put(new PhoneNum(1,1,1),“obj1”); 我put进一个key为PhoneNum对象**,Object value=hashMap.get(new PhoneNum(1,1,1));** 现在我又想获取值。其实在这里我new了两个实例,hashCode是不一样的,HashMap又是根据hashCode定位散列桶的位置,这时候,就会出现存的在这个桶,但是在另一个桶找,肯定是找不到的,HashMap就会返回null;那如果存跟找正好在一个桶里呢,HashMap也会优先比对hashCode,如果hashCode不匹配,也就不会去验证对象的等同行,直接返回null。

所以,会有重写equals方法一定要重写hashCode的规则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值