equals()与hashCode()之间的关系
目录
概述
equals 方法
hashCode 方法
为什么必须同时重写equals()与hashCode()
拓展 :HashMap 与 HashSet 的区别
概述
-
概述:equals 与 hashCode 是 Java 中的 Object 类的两个核心方法,主要用于对象比较和哈希表操作,两者之间紧密关联,equals 与 hashCode 是 Java 中必须共同遵守的一个约定,正确实现它们,可以确保 HashMap、HashSet 等哈希集合正常工作。
-
Object 常见方法
{ // 返回此对象的运行时类public final native Class<?> getClass();// 返回对象的哈希码值public native int hashCode();// 判断两个对象是否相等public boolean equals(Object obj) {return (this == obj);}// 创建并返回此对象的一个副本protected native Object clone() throws CloneNotSupportedException;// 返回对象的字符串表示public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}// 唤醒在此对象监视器上等待的单个线程public final native void notify();// 唤醒在此对象监视器上等待的所有线程public final native void notifyAll();// 让当前线程等待,直到另一个线程调用此对象的notify()或notifyAll()方法public final native void wait(long timeout) throws InterruptedException;// 带纳秒精度的wait方法public final void wait(long timeout, int nanos) throws InterruptedException // 无限期等待public final void wait() throws InterruptedException // 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法protected void finalize() throws Throwable { }
}
equals 方法
-
基本概述: equals 用于判断两个对象的内容是否相等,即逻辑相等性而不是物理地址相等,同时不能判断基本数据类型的变量,Object 类默认情况下,
equals等价于==,即比较的是内存地址,所以实际业务中,我们需要对 equals 方法进行重写操作。 -
object 类中默认实现
public boolean equals(Object obj) {return (this == obj); // 比较两个对象的内存地址(即物理地址)
}
hashCode 方法
-
基本概述:hashCode 主要是可以将数据通过一个算法映射成一个固定大小的值,即哈希值,主要用于哈希表(例如 HashMap、HashSet 等哈希集合)快速定位对象存储位置的,提高查询效率。
-
object 类中默认实现
public native int hashCode(); // 本地方法,通常根据对象的内存地址计算哈希码
为什么必须同时重写equals()与hashCode()
首先要知道两个相等的对象 hashCode 值必须相等,Object 类是 Java 中所有类的终极父类,默认情况下 hashCode 是基于内存地址计算的,且默认情况下 equals 也是与==等价的,如果重写了 equals 而没有重写 hashCode,那么在判断两个对象是否相等时,a.equals(b)为 true,但是因为hashCode 默认情况下是基于内存地址计算,最终因不同地址而返回不同的值,因此 a.hashCode() 与 b.hashCode()不相等,这直接违反了 Java 的核心约定(equals 为 true 的对象,hashCode 必须相等)。这种违反约定的行为,在使用哈希表相关集合(HashMap、HashSet、LinkedHashMap等)时会引发致命问题。例如可能 HashMap 中明明存在某数据,但因违反约定,而查找失效;
不同时重写导致问题的例子:
-
当你把两个逻辑相等的对象存入 HashSet时,HashSet会先通过 hashCode 计算存储位置(“桶”):由于两者 hashCode 不同,会被分配到不同的桶中,后续即使通过 equals 判断是同一个对象,也不会被视为重复元素,最终导致 HashSet 中出现重复数据(违背去重特性)。
-
当你用逻辑相等的对象作为 HashMap 的 key 查找或存入时,同样会因为 hashCode 不同,无法定位到同一个桶,导致无法找到已存入的键值对(查找失效),或重复存入相同逻辑的 key(破坏 key 唯一性)。
-
即使是遍历哈希表,也可能因为对象的 hashCode 与 equals 逻辑不匹配,出现遍历结果混乱、元素丢失等问题。
总之:
- 如果
a.equals(b) == true,则a.hashCode() == b.hashCode()必须成立。 - 反之,如果
a.hashCode() == b.hashCode(),a.equals(b)可以返回 false(允许哈希冲突)。
拓展 :HashMap 与 HashSet 的区别
| 特性 | HashMap | HashSet |
| 实现接口 | Map接口 | Set接口 |
| 存储内容 | 键值对 | 仅存储对象,且实际上,这个对象作为HashMap的键 |
| 重复元素 | 不允许重复的键,但允许重复的值 | 不允许任何重复的元素 |
| 空值处理 | 允许 | 允许为 |
| 添加方法 | put(K key,V value) | add(E e) |
| 内部实现 | 它本身就是一个哈希表,存储 Entry | 内部使用了一个HashMap,你添加的元素被用作这个内部HashMap的键,而值则是一个固定的虚拟对象 |
| 操作方式 | 使用 get(key)通过键来获取值 | 使用 contain(e)检查元素是否存在 |
-
小结:HashSet是基于HashMap实现的,当你向 HashSet 添加一个元素时,实际上是将这个元素作为键放入了一个内部的 HashMap 中,而对应的值则是一个固定的、无意义的对象。正因为 HashMap 的键是唯一的,所以 HashSet 天然地保证了元素的唯一性。
结尾
Java 中 equals 与 hashCode 的设计,本质是 “逻辑相等定义” 与 “哈希存储优化” 的协同 —— 前者明确对象 “是否相同”,后者为集合提供 “快速定位” 的基础,而 HashMap 与 HashSet 则是这种协同设计的典型应用。
实际开发中,我们无需手动编写复杂的重写逻辑(可通过 Lombok 的 @EqualsAndHashCode 注解自动生成,确保规范),但必须牢记核心约定:重写 equals 必重写 hashCode,且两者基于相同的关键属性实现。
这些基础概念看似简单,却直接影响代码的正确性与性能 —— 比如忽略约定会导致 HashSet 去重失效、HashMap 查找异常,这些问题在线上环境中往往难以排查。因此,夯实这些底层逻辑,不仅能避免初级错误,更能帮助我们理解集合、框架的设计思想,为后续深入学习打下坚实基础。希望本文能让你对这些核心概念的理解更透彻。
