面试官:换人!他连哈希扣的都不懂...

本文详细探讨了Java中的hashCode和equals方法,包括它们的作用、哈希表的概念、hashCode的重要性以及重写equals和hashCode的原因。讲解了hashCode是对象经过哈希运算后的整型值,用于哈希表(如HashMap)中快速定位对象。当对象内容相同时,重写equals以确保返回true。同时,重写hashCode是为了配合哈希表的高效查询和插入。不重写可能会导致在哈希表中查找元素失败。文章最后阐述了hashCode相等时,equals不一定相等,但equals相等则hashCode一定相等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

相信你面试的时候,肯定被问过 hashCode 和 equals 相关的问题 。如:

  • hashCode 是什么?它是怎么得来的?有什么用?
  • 经典题,equals 和 == 有什么区别?
  • 为什么要重写 equals 和 hashCode ?
  • 重写了 equals ,就必须要重写 hashCode 吗?为什么?
  • hashCode 相等时,equals 一定相等吗?反过来呢?

好的,上面就是灵魂拷问环节。其实,这些问题仔细想一下也不难,主要是平时我们很少去思考它。

正文

下面就按照上边的问题顺序,一个一个剖析它。扒开 hashCode 的神秘面纱。

什么是 hashCode?

我们通常说的 hashCode 其实就是一个经过哈希运算之后的整型值。而这个哈希运算的算法,在 Object 类中就是通过一个本地方法 hashCode() 来实现的(HashMap 中还会有一些其它的运算)。

public native int hashCode();

可以看到它是一个本地方法。那么,想要了解这个方法到底是用来干嘛的,最直接有效的方法就是,去看它的源码注释。

下边我就用我蹩脚的英文翻译一下它的意思。。。

返回当前对象的一个哈希值。这个方法用于支持一些哈希表,例如 HashMap 。

通常来讲,它有如下一些约定:

  • 若对象的信息没有被修改,那么,在一个程序的执行期间,对于相同的对象,不管调用多少次 hashCode 方法,都应该返回相同的值。当然,在相同程序的不同执行期间,不需要保持结果一致。
  • 若两个对象的 equals 方法返回值相同,那么,调用它们各自的 hashCode 方法时,也必须返回相同的结果。(ps: 这句话解答了上边的一些问题,后面会用例子来证明这一点)
  • 当两个对象的 equals 方法返回值不同时,那么它们的 hashCode 方法不用保证必须返回不同的值。但是,我们应该知道,在这种情况下,我们最好也设计成 hashCode 返回不同的值。因为,这样做有助于提高哈希表的性能。

在实际情况下,Object 类的 hashCode 方法在不同的对象中确实返回了不同的哈希值。这通常是通过把对象的内部地址转换为一个整数来实现的。

ps: 这里说的内部地址就是指物理地址,也就是内存地址。需要注意的是,虽然 hashCode 值是依据它的内存地址而得来的。但是,不能说 hashCode 就代表对象的内存地址,实际上,hashCode 地址是存放在哈希表中的。

上边的源码注释真可谓是句句珠玑,把 hashCode 方法解释的淋漓尽致。一会儿我通过一个案例说明,就能明白我为什么这样说了。

什么是哈希表?

上文中提到了哈希表。什么是哈希表呢?我们直接看百度百科的解释。

用一张图来表示它们的关系。

左边一列就是一些关键码(key),通过哈希函数,它们都会得到一个固定的值,分别对应右边一列的某个值。右边的这一列就可以认为是一张哈希表。

而且,我们会发现,有可能有些 key 不同,但是它们对应的哈希值却是一样的,例如 aa,bb 都指向 1001 。但是,一定不会出现同一个 key 指向不同的值。

这也非常好理解,因为哈希表就是用来查找 key 的哈希地址的。在 key 确定的情况下,通过哈希函数计算出来的 哈希地址,一定也是确定的。如图中的 cc 已经确定在 1002 位置了,那么就不可能再占据 1003 位置。

思考一下,如果有另外一个元素 ee 来了,它的哈希地址也落在 1002 位置,怎么办呢?

hashCode 有什么用?

其实,上图就已经可以说明一些问题了。我们通过一个 key 计算出它的 hashCode 值,就可以唯一确定它在哈希表中的位置。这样,在查询时,就可以直接定位到当前元素,提高查询效率。

现在我们假设有这样一个场景。我们需要在内存中的一块儿区域存放 10000 个不同的元素(以aa,bb,cc,dd 等为例)。那怎么实现不同的元素插入,相同的元素覆盖呢?

我们最容易想到的方法就是,每当存一个新元素时,就遍历一遍已经存在的元素,看有没有相同的。这样虽然也是可以实现的,但是,如果已经存在了 9000 个元素,你就需要去遍历一下这 9000 个元素。很明显,这样的效率是非常低下的。

我们转换一种思路,还是以上图为例。若来了一个新元素 ff,首先去计算它的 hashCode 值,得出为 1003 。发现此处还没有元素,则直接把这个新元素 ff 放到此位置。

然后,ee 来了,通过计算哈希值得到 1002 。此时,发现 1002 位置已经存在一个元素了。那么,通过 equals 方法比较它们是否相等,发现只有一个 dd 元素,很明显和 ee 不相等。那么,就把 ee 元素放到 dd 元素的后边(可以用链表形式存放)。

我们会发现,当有新元素来的时候,先去计算它们的哈希值,再去确定存放的位置,这样就可以减少比较的次数。如 ff 不需要比较, ee 只需要和 dd 比较一次。

当元素越来越多的时候,新元素也只需要和当前哈希值相同的位置上,已经存在的元素进行比较。而不需要和其他哈希值不同的位置上的元素进行比较。这样就大大减少了元素的比较次数。

图中为了方便,画的哈希表比较小。现在假设,这个哈希表非常的大,例如有这么非常多个位置,从 1001 ~ 9999。那么,新元素插入的时候,有很大概率会插入到一个还没有元素存在的位置上,这样就不需要比较了,效率非常高。但是,我们会发现这样也有一个弊端,就是哈希表所占的内存空间就会变大。因此,这是一个权衡的过程。

有心的同学可能已经发现了。我去,上边的这个做法好熟悉啊。没错,它就是大名鼎鼎的 HashMap 底层实现的思想。对 HashMap 还不了解的,赶紧看这篇文章理一下思路。HashMap 底层实现原理及源码分析

所以,hashCode 有什么用。很明显,提高了查询,插入元素的效率呀。

equals 和 == 有什么区别?

这是万年不变,经久不衰的经典面试题了。让我油然想起,当初为了面试,背诵过的面经了,简直是一把心酸一把泪。现在还能记得这道题的标准答案:equals 比较的是内容, == 比较的是地址。

当时,真的就只是背答案,知其然而不知其所以然。再往下问,为什么要重写 equals ,就懵逼了。

首先,我们应该知道 equals 是定义在所有类的父类 Object 中的。

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

可以看到,它的默认实现,就是 == ,这是用来比较内存地址的。所以,如果一个对象的 equals 不重写的话,和 == 的效果是一样的。

我们知道,当创建两个普通对象时,一般情况下,它们所对应的内存地址是不一样的。例如,我定义一个 User 类。

public class User {
   
    private String name;
    private int age;

    public String getName() {
   
        return name;
    }

    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值