JDK1.8源码-java.lang.object

在使用自定义对象作为HashMap的Key时,必须重写equals和hashCode方法。hashCode方法用于确定对象在哈希表中的位置,以提高查找效率。如果两个对象equals相等,它们的hashCode必须相同;不相等时,hashCode可以相同但不强制要求。不重写可能导致元素插入错误和性能下降。

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

1.hashcode()方法
在hashmap中如果用自己自定义的对象作为Key,必须重写hashcode()方法,equals()方法
hashCode 在 Object 类中定义如下:

public native int hashCode();

这也是一个用 native 声明的本地方法,作用是返回对象的散列码,是 int 类型的数值。

那么这个方法存在的意义是什么呢?

我们知道在Java 中有几种集合类,比如 List,Set,还有 Map,List集合一般是存放的元素是有序可重复的,Set 存放的元素则是无序不可重复的,而 Map 集合存放的是键值对。
  前面我们说过判断一个元素是否相等可以通过 equals 方法,没增加一个元素,那么我们就通过 equals 方法判断集合中的每一个元素是否重复,但是如果集合中有10000个元素了,但我们新加入一个元素时,那就需要进行10000次equals方法的调用,这显然效率很低。
  于是,Java 的集合设计者就采用了 哈希表 来实现。哈希算法也称为散列算法,是将数据依特定算法产生的结果直接指定到一个地址上。这个结果就是由 hashCode 方法产生。这样一来,当集合要添加新的元素时,先调用这个元素的 hashCode 方法,就一下子能定位到它应该放置的物理位置上。

①、如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;

②、如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了;

③、不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起(很少出现)。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

在这里插入图片描述

这里有 A,B,C,D四个对象,分别通过 hashCode 方法产生了三个值,注意 A 和 B 对象调用 hashCode 产生的值是相同的,即 A.hashCode() = B.hashCode() = 0x001,发生了哈希冲突,这时候由于最先是插入了 A,在插入的B的时候,我们发现 B 是要插入到 A 所在的位置,而 A 已经插入了,这时候就通过调用 equals 方法判断 A 和 B 是否相同,如果相同就不插入 B,如果不同则将 B 插入到 A 后面的位置。所以对于 equals 方法和 hashCode 方法有如下要求:

一、hashCode 要求
①、在程序运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
  
②、通过equals调用返回true 的2个对象的hashCode一定一样。

③、通过equasl返回false 的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。

因此我们可以得到如下推论:

两个对象相等,其 hashCode 一定相同;

两个对象不相等,其 hashCode 有可能相同;

hashCode 相同的两个对象,不一定相等;

hashCode 不相同的两个对象,一定不相等;

这四个推论通过上图可以更好的理解。

可能会有人疑问,对于不能重复的集合,为什么不直接通过 hashCode 对于每个元素都产生唯一的值,如果重复就是相同的值,这样不就不需要调用 equals 方法来判断是否相同了吗?
  实际上对于元素不是很多的情况下,直接通过 hashCode 产生唯一的索引值,通过这个索引值能直接找到元素,而且还能判断是否相同。比如数据库存储的数据,ID 是有序排列的,我们能通过 ID 直接找到某个元素,如果新插入的元素 ID 已经有了,那就表示是重复数据,这是很完美的办法。但现实是存储的元素很难有这样的 ID 关键字,也就很难这种实现 hashCode 的唯一算法,再者就算能实现,但是产生的 hashCode 码是非常大的,这会大的超过 Java 所能表示的范围,很占内存空间,所以也是不予考虑的。

二、hashCode 编写指导:
①、不同对象的hash码应该尽量不同,避免hash冲突,也就是算法获得的元素要尽量均匀分布。

②、hash 值是一个 int 类型,在Java中占用 4 个字节,也就是 232 次方,要避免溢出。

在 JDK 的 Integer类,Float 类,String 类等都重写了 hashCode 方法,我们自定义对象也可以参考这些类来写。

下面是 JDK String 类的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;
}

再次提醒大家,对于 Map 集合,我们可以选取Java中的基本类型,还有引用类型 String 作为 key,因为它们都按照规范重写了 equals 方法和 hashCode 方法。

但是如果你用自定义对象作为 key,那么一定要重写 equals 方法和 hashCode 方法,不然会有意想不到的错误产生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值