如果hashMap的key是一个自定义的类,怎么办?为什么重写equals还要重写hashcode

本文详细解释了在Java中如何正确地实现自定义类作为HashMap的键,包括重写hashcode()和equals()方法的重要性及原因。阐述了不重写这些方法可能导致的问题,并解释了为何必须同时重写两者。

如果hashMap的key是一个自定义的类,必须重写该类的hashcode()方法和equals()方法

 

 HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地 址,这样即便有相同含义的两个对象,比较也是不相等的,equals()比较的是内存地址是否相等。例如,

Student st1 = new Student("wei","man");

Student st2 = new Student("wei","man"); 

正常理解这两个对象再存入到hashMap中应该是相等的,但如果你不重写 hashcode()方法的话,比较是其地址,不相等!

      HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等 的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为 同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。

 

 

如果不被重写(原生)的hashCode和equals是什么样的?

  1.       不被重写(原生)的hashCode值是根据内存地址换算出来的一个值。
  2.       不被重写(原生)的equals方法是严格判断一个对象是否相等的方法(object1 == object2)。

 

 

     为什么重写equals还要重写hashcode?

  因为重写的equal()里一般比较的比较全面比较复杂,这样效率就比较低,而利用hashCode()进行对比,则只要生成一个hash值进行比较就可以了,效率很高,那么hashCode()既然效率这么高为什么还要equal()呢?

  因为hashCode()并不是完全可靠,有时候不同的对象他们生成的hashcode也会一样(生成hash值得公式可能存在的问题),所以hashCode()只能说是大部分时候可靠,并不是绝对可靠,所以我们可以得出:

 

         1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。

         2.hashCode()相等的两 个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。

   因为  如果两个对象相等(equal),它们的hashcode一定相同 ;而 对象相等 是通过 equals() 推出的。如果重写了 equals() 令两个原本 不相同的对象 A和B 在新的equals()的规则下是相同的对象。那么就要重写二者的hashCode()使得二者的hashcode一致。否则,原本不相同的两对象其hashcode不一定是相同的。如果不重写,就可能会出现“相等”(在新的equals()情况下相等)的两对象其hashcode不相同。

 

 

 

<think>我们正在讨论在自定义中同时重写equalshashCode方法的原因。根据用户提供的引用和之前的问题,我们可以总结如下: 在自定义中同时重写equalshashCode方法的原因主要有以下几点: 1. **确保集合操作的正确性**:当自定义对象被放入基于哈希的集合(如HashSet、HashMap)时,这些集合依赖于hashCode方法来确定对象的存储位置(桶),并依赖equals方法来判断两个对象是否相等。如果不重写这两个方法,集合可能无法正确识别对象的相等性,导致逻辑错误(例如,两个内容相同的对象被当作不同的对象存储,或者反过来)[^2][^3][^4]。 2. **维护hashCodeequals的契约**:Java规范要求,如果两个对象通过equals方法判断为相等,那么它们的hashCode方法必须返回相同的值。反之,如果两个对象的hashCode不同,那么它们一定不相等(但hashCode相同并不强制要求对象相等,因为可能存在哈希冲突)。如果不遵循这一契约,将会破坏基于哈希的集合的正常工作。例如,如果重写equals方法但没有重写hashCode方法,可能导致两个相等的对象拥有不同的哈希码,进而被放入哈希表的不同桶中,使得集合中同时存在两个逻辑上相等的对象,破坏了集合的唯一性[^1][^4]。 3. **避免数据丢失和逻辑错误**:在哈希集合中,当添加一个对象时,首先根据其hashCode计算桶的位置,然后在该桶中使用equals方法检查是否已存在相等的对象。如果存在,则不会重复添加。如果hashCode方法未被正确重写,相等的对象可能被放入不同的桶,从而导致重复添加;或者不相等的对象被放入同一个桶,但通过equals方法比较后认为不相等,这样虽然不会导致重复,但会增加冲突,降低性能[^3][^4]。 4. **性能优化**:虽然主要目的是正确性,但良好的hashCode方法实现也能提升性能。一个好的hashCode方法应尽可能减少冲突,这样在哈希集合中查找元素时,可以快速定位到桶,并在桶内进行少量比较(理想情况下只有一次equals比较)即可确定对象是否存在。如果哈希码分布不均匀,会导致某些桶中的链表很长,从而降低查找效率(从O(1)退化为O(n))[^1][^5]。 5. **保证一致性**:在对象的生命周期内,如果对象的参与equals比较的属性没有改变,那么多次调用hashCode方法应返回相同的值。因此,重写hashCode方法时,应基于那些在对象生命周期中不变的属性,或者确保当对象被放入哈希集合后,这些属性不被修改。否则,如果对象被修改导致hashCode改变,那么在哈希集合中可能无法再次找到该对象,造成内存泄漏或错误[^5]。 具体到代码实现,引用[5]给出了重写equalshashCode的示例,并强调: - 使用相同的属性集合来生成hashCodeequals(即两个方法所依赖的属性必须一致)。 - 在equals方法中,要检查型、空值、自比较等,并逐个比较关键属性。 - 在hashCode方法中,通常使用Objects.hash()方法,传入所有参与equals比较的属性,以确保契约被满足。 例如,对于一个自定义User,有两个属性userName和password,我们可以这样重写: ```java public class User { private String userName; private String password; // 构造方法等 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(userName, user.userName) && Objects.equals(password, user.password); } @Override public int hashCode() { return Objects.hash(userName, password); } } ``` 如果不这样重写,可能会出现如引用[3]所述的情况:在一个HashSet中,两个内容相同的User对象(userName和password都相同)被当作不同的对象存储,导致HashSet中出现重复元素。 因此,同时重写equalshashCode方法是保证自定义在哈希集合中正确工作的必要措施。 §§相关问题§§ 1. 如何正确重写自定义的`equals`和`hashCode`方法? 2. 如果不重写`hashCode`方法,会对`HashMap`的操作产生什么影响? 3. 为什么重写`equals`方法时必须考虑自反性、对称性、传递性和一致性? </think>### 在自定义中同时重写 `equals` 和 `hashCode` 方法的原因 在自定义中**必须同时重写** `equals` 和 `hashCode` 方法的核心原因是为了维护 Java 对象的 **一致性契约**,确保基于哈希的集合(如 `HashSet`, `HashMap`)能正确工作。具体原理如下: --- #### 1. **维护哈希契约(Hash Contract)** - **契约要求**:若 `a.equals(b)` 为 `true`,则 `a.hashCode() == b.hashCode()` 必须成立 - **违反后果**:若不重写 `hashCode`: ```java User user1 = new User("Alice", "pass123"); User user2 = new User("Alice", "pass123"); System.out.println(user1.equals(user2)); // true(若重写equals) System.out.println(user1.hashCode() == user2.hashCode()); // false(未重写hashCode) ``` 此时 `HashSet` 会将二者视为不同对象,导致重复数据进入集合[^3][^4] --- #### 2. **保障哈希集合的正确性** | 方法缺失 | `HashSet` 行为异常 | 后果 | |-------------------|---------------------------------------------|--------------------------| | 不重写 `hashCode` | 相同内容对象被分配到不同哈希桶 | 无法去重,出现重复元素[^3] | | 不重写 `equals` | 不同内容对象因哈希冲突被误判为相同 | 数据覆盖或丢失[^2] | 示例:若 `User` 未正确重写方法 ```java Set<User> users = new HashSet<>(); users.add(new User("Bob", "123")); users.add(new User("Bob", "123")); // 被错误地添加为第二个元素 ``` --- #### 3. **避免逻辑冲突** - **默认行为问题**: - `Object.hashCode()` 基于内存地址生成 - `Object.equals()` 比较内存地址 - **自定义需求**: ```java @Override public int hashCode() { return Objects.hash(name, age); // 基于业务属性[^5] } @Override public boolean equals(Object o) { if (this == o) return true; // 自反性检查[^1] if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(name, user.name) && Objects.equals(age, user.age); // 属性比较[^5] } ``` 确保相同业务属性的对象具有相同哈希码和相等性判定[^4] --- #### 4. **性能优化** - `HashMap`/`HashSet` 的工作流程: 1. 用 `hashCode()` 快速定位桶位置($O(1)$) 2. 用 `equals()` 精确比较桶内元素($O(k)$, ${k}$ 为桶大小) - **正确重写可避免**: - 哈希冲突过多导致桶内链表过长(退化为 $O(n)$ 查找) - 哈希码不均匀导致数据倾斜 --- ### 关键实践原则 1. **同步重写**:重写 `equals` 必须重写 `hashCode`,反之亦然[^4] 2. **属性一致性**:两个方法应基于相同的业务属性集合[^5] 3. **不可变依赖**:用作哈希码计算的属性应是不可变的 4. **空值安全**:处理属性为 `null` 的情况(如 `Objects.hash()` 自动处理[^5]) 5. **ORM 特别提示**:使用 getter 而非直接字段引用(避免延迟加载问题[^5]) > ⚠️ **典型错误示例** > 若只重写 `equals` 不重写 `hashCode`,会导致 `HashMap` 的 `get()` 方法无法找到已存入的值: > ```java > Map<User, String> map = new HashMap<>(); > User key = new User("Kate", "admin"); > map.put(key, "VIP"); > > // 返回 null!因 hashCode 不匹配 > System.out.println(map.get(new User("Kate", "admin"))); > ``` --- ### 总结机制 ```mermaid graph LR A[添加对象到HashSet] --> B{计算hashCode确定桶位置} B --> C[桶为空?] C -->|是| D[直接存入] C -->|否| E[遍历桶内元素] E --> F{调用equals逐个比较} F -->|相等| G[拒绝添加] F -->|不相等| H[存入链表/树] ``` **双重保障机制**:`hashCode` 实现快速定位,`equals` 实现精确去重,二者缺一不可[^2][^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值