为什么重写equals方法也要重写hashcode方法

为何覆盖equals须同步覆盖hashCode
本文解释了在Java中,当重写equals方法时,为何必须同时重写hashCode方法的原因。通过HashMap的工作原理说明,如果不同时重写,可能会导致违反hashCode的通用约定,进而使基于散列的集合类无法正常运作。

转https://blog.youkuaiyun.com/wusuopubupt/article/details/77162126

为什么在覆盖equals时一定也要覆盖hashCode?

因为先hashmap, 后equals !

下面用HashMap来阐述原因,首先假设key1和key2的值在业务逻辑领域是相等的,即它们应该是同一个对象,HashMap已经存储了key1,现在要查找key2是否存在,正确的结果应该是存在。

Java中的HashMap实际上是一个链表数组,即首先HashMap是一个数组,然后数组中的每一个元素是一个链表(更通用的概念可以称为桶bucket,Java中的HashMap用Entry类描述链表的结点结构),HashMap在执行Put,Contains之类的操作时,会首先根据你提供的Key计算hashCode值,然后根据这个hashCode值在数组中找到某一个链表或桶(通常是找到链表的起始结点),这一步操作利用了hashCode()方法,如果你覆盖了就会用你提供的方法,在找到某一个链表的起始结点后,就会遍历链表,然后通过equals方法来寻找是否存在与Key的值相等的结点,如果执行equals方法后的结果相等,HashMap就认为已经存在这个元素,这一步如果你覆盖了equals方法就会用到你提供的equals方法。

通过上面的描述,我们发现equals方法和hashCode方法如果不同时按你自己逻辑覆盖的话,HashMap就会出问题。比如你只覆盖了equals方法而没有覆盖hashCode方法,那么HashMap在第一步寻找链表的时候会出错,有同样值的两个对象Key1和Key2并不会指向同一个链表或桶,因为你没有提供自己的hashCode方法,那么就会使用Object的hashCode方法,该方法是根据内存地址来比较两个对象是否一致,由于Key1和Key2有不桶的内存地址,所以会指向不同的链表,这样HashMap会认为key2不存在,虽然我们期望Key1和Key2是同一个对象;反之如果只覆盖了hashCode方法而没有覆盖equals方法,那么虽然第一步操作会使Key1和Key2找到同一个链表,但是由于equals没有覆盖,那么在遍历链表的元素时,key1.equals(key2)也会失败(事实上Object的equals方法也是比较内存地址),从而HashMap认为不存在Key2对象,这同样也是不正确的。

以下内容摘自Effective Java:

覆盖equals时总要覆盖hashCode,一个很常见的错误根源在没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,这样的集合包括HashMap、HashSet和Hashtable。

下面是约定的内容,摘自Object规范[JavaSE6]:

  • 在应用程序的执行期间,只要对象的equals方法所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

  • 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。

  • 如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中的任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该知道,给不相同的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。

<think>首先,用户的问题是:“我想理解在Java重写equals方法时为何需要同时重写hashCode方法 请问Java重写equals方法为什么要同时重写hashCode方法”。我需要基于系统级指令来回答。 系统级指令: - 所有行内数学表达式使用 `$...$` 格式。 - 独立公式使用 `$$...$$` 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如 [^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用:用户提供了几个引用,我需要参考它们,但不是直接复制,而是基于内容整合。引用是关于为什么重写equals时必须重写hashCode的。 关键点从引用中提取: - [^1]:重写equalshashCode的基本规则,两者关系,只重写一个会导致问题。 - [^2]:hashCode默认行为产生独特值,如果不重写,对象即使相等也可能有不同hashCode。 - [^3]:equalshashCode在哈希表(如HashMap、HashSet)中相关,必须一致。 - [^4]:重写equals必须重写hashCode以确保相等的对象有相同的哈希值,否则在哈希集合中出错。 我的回答结构: 1. **介绍问题**:解释equalshashCode的作用。 2. **为什么需要同时重写**:基于哈希表的机制。 3. **潜在问题**:如果不重写hashCode,只重写equals,会发生什么。 4. **规则和最佳实践**:如何正确重写两者。 5. **总结**:强调重要性。 6. **生成相关问题**:在最后添加相关问题。 使用中文回答。 添加引用标识:在相关段落末尾添加 [^1]、[^2] 等,基于提供的引用。 数学表达式:用户问题不涉及数学,但如果有数学内容,确保格式正确。这里可能不需要数学表达式,除非类比或解释概念。 逐步回答: - 步骤1:解释equals方法的作用:比较对象内容是否相等。 - 步骤2:解释hashCode方法的作用:返回对象的哈希码,用于哈希表。 - 步骤3:说明在Java中,哈希表如HashMap、HashSet依赖hashCode来存储和检索对象。如果两个对象相等(equals返回true),它们必须有相同的hashCode。 - 步骤4:如果只重写equals而不重写hashCode,可能导致相等的对象有不同hashCode,违反hashCode契约,导致哈希表行为错误。 - 步骤5:给出一个简单例子来演示问题。 - 步骤6:总结规则。 生成相关问题:在最后添加2-3个相关问题。 完整回答结构: - 开头:直接回答问题。 - 主体:分点解释。 - 结尾:总结和生成相关问题。</think>### Java重写equals方法时为何需要同时重写hashCode方法Java中,重写`equals`方法时必须同时重写`hashCode`方法,这是为了确保对象在哈希表(如`HashMap`、`HashSet`等)中的行为正确性。下面我将逐步解释原因,并基于Java规范和相关最佳实践进行说明。 #### 1. **equalshashCode方法的作用** - **equals方法**:用于比较两个对象的内容是否相等。默认情况下(从`Object`类继承),它比较对象的内存地址(即引用相等),但重写后可以基于对象的属性(如字段值)进行比较[^3]。 - **hashCode方法**:返回一个整数哈希码,用于在哈希表中快速定位对象。默认行为基于对象的内存地址生成唯一值[^2]。 - 关键关系:Java规范要求,如果两个对象通过`equals`方法比较为相等(`obj1.equals(obj2) == true`),那么它们的哈希码必须相同(`obj1.hashCode() == obj2.hashCode()`)。这称为“hashCode契约”[^1][^4]。 #### 2. **为什么必须同时重写?** 如果不重写`hashCode`而只重写`equals`,会导致哈希表行为不一致,原因如下: - **哈希表依赖hashCode**:在`HashMap`或`HashSet`中,对象首先通过`hashCode`分配到“桶”(bucket)中,然后使用`equals`在桶内比较。如果两个对象相等但`hashCode`不同,它们会被分配到不同桶中,导致`equals`比较无法触发,从而无法正确识别相等对象[^3][^4]。 - **违反契约的风险**:只重写`equals`而不重写`hashCode`时,相等的对象可能生成不同的哈希码(因为默认`hashCode`基于内存地址)。这破坏了Java的契约规则,可能引发逻辑错误,例如对象在集合中重复存储或无法查找[^1][^2]。 - **示例说明**:假设有一个`Person`类,重写了`equals`方法基于`name`字段比较: ```java class Person { String name; // 重写equals:基于name比较 @Override public boolean equals(Object obj) { if (obj instanceof Person) { return this.name.equals(((Person) obj).name); } return false; } // 未重写hashCode } ``` 如果创建两个`name`相同的`Person`对象(`person1`和`person2`),`equals`返回`true`,但默认`hashCode`不同。当存入`HashSet`时: ```java Set<Person> set = new HashSet<>(); set.add(person1); set.add(person2); // 可能成功添加,因为hashCode不同导致分配到不同桶 ``` 结果:`set`大小可能为2,尽管对象内容相等。这违反了集合的唯一性要求[^4]。 #### 3. **如何正确重写两者** - **基本规则**: - 重写`equals`时,确保比较对象的所有相关属性。 - 重写`hashCode`时,必须使用与`equals`相同的属性计算哈希码,保证相等的对象产生相同的哈希值[^1]。 - 常用方法:在`hashCode`中使用`Objects.hash()`工具类,例如: ```java @Override public int hashCode() { return Objects.hash(name); // 基于name字段计算哈希码 } ``` - **最佳实践**:在IDE(如IntelliJ IDEA)中生成`equals`和`hashCode`方法,确保一致性。这避免了手动错误,并符合Java规范[^3][^4]。 #### 4. **总结** 重写`equals`时必须重写`hashCode`,以维护哈希表的正确性和Java契约。如果不这样做,可能导致集合类(如`HashMap`)出现不可预测的行为,例如对象丢失或重复存储。始终确保两者同步,基于相同的属性实现[^1][^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值