Java 中的 hashCode () 与 equals () 核心原理、契约规范、重写实践与面试全解

本系列可作为JAVA学习系列的笔记,文中提到的一些练习的代码,小编会将代码复制下来,大家复制下来就可以练习了,方便大家学习。

点赞关注不迷路!您的点赞、关注和收藏是对小编最大的支持和鼓励!

系列文章目录

JAVA初阶---------已更完

JAVA数据结构 DAY1-集合和时空复杂度

JAVA数据结构 DAY2-包装类和泛型

JAVA数据结构 DAY3-List接口

JAVA数据结构 DAY4-ArrayList

JAVA数据结构 DAY5-LinkedList

JAVA数据结构 DAY6-栈和队列

JAVA数据结构 DAY7-二叉树

JAVA数据结构 DAY8-堆

JAVA数据结构 DAY9 equals、Comparable、Comparator 与 PriorityQueue 深度解析

JAVA数据结构 DAY10-排序

JAVA数据结构 DAY11-Set和Map


拓展目录

手把手教你用 ArrayList 实现杨辉三角:从逻辑推导到每行代码详解

链表高频 6 题精讲 | 从入门到熟练掌握链表操作

二叉树高频题精讲 | 从入门到熟练掌握二叉树操作

二叉树高频题精讲 | 从入门到熟练掌握二叉树操作2

二叉树高频题精讲 | 从入门到熟练掌握二叉树操作3

Java 中的 hashCode () 与 equals () 核心原理、契约规范、重写实践与面试全解


目录

目录

系列文章目录

拓展目录

目录

前言

[一、溯源:Object 类中的 equals () 与 hashCode ()](#一、溯源:Object 类中的 equals () 与 hashCode ())

[1.1 equals () 方法的官方定义与默认实现](#1.1 equals () 方法的官方定义与默认实现)

官方源码

核心解读

官方规范(JavaDoc)

[1.2 hashCode () 方法的官方定义与默认实现](#1.2 hashCode () 方法的官方定义与默认实现)

官方源码

核心解读

官方规范(JavaDoc)

[二、核心对比:equals () 与 hashCode () 的本质区别](#二、核心对比:equals () 与 hashCode () 的本质区别)

通俗比喻(最易理解)

[三、黄金契约:equals () 与 hashCode () 的三大绑定规则](#三、黄金契约:equals () 与 hashCode () 的三大绑定规则)

[契约 1:equals () 相等 → hashCode () 必须相等](#契约 1:equals () 相等 → hashCode () 必须相等)

[契约 2:hashCode () 相等 → equals () 不一定相等](#契约 2:hashCode () 相等 → equals () 不一定相等)

[契约 3:对象不变 → hashCode () 永远不变](#契约 3:对象不变 → hashCode () 永远不变)

[四、致命问题:只重写 equals () 不重写 hashCode () 的灾难](#四、致命问题:只重写 equals () 不重写 hashCode () 的灾难)

[4.1 测试场景](#4.1 测试场景)

[4.2 运行结果与问题分析](#4.2 运行结果与问题分析)

[4.3 根本原因](#4.3 根本原因)

[4.4 解决方案:同时重写两个方法](#4.4 解决方案:同时重写两个方法)

[五、源码深度解析:HashMap 如何依赖 equals () 和 hashCode ()?](#五、源码深度解析:HashMap 如何依赖 equals () 和 hashCode ()?)

[5.1 HashMap 存储 / 查找核心流程](#5.1 HashMap 存储 / 查找核心流程)

[5.2 JDK 1.8 HashMap 核心源码(简化版)](#5.2 JDK 1.8 HashMap 核心源码(简化版))

[1. 计算哈希值](#1. 计算哈希值)

[2. 查找元素(get 方法)](#2. 查找元素(get 方法))

[5.3 源码核心结论](#5.3 源码核心结论)

[六、最佳实践:如何正确重写 equals () 和 hashCode ()?](#六、最佳实践:如何正确重写 equals () 和 hashCode ()?)

[6.1 重写核心原则](#6.1 重写核心原则)

[6.2 IDEA 一键生成教程](#6.2 IDEA 一键生成教程)

[6.3 标准重写模板(通用版)](#6.3 标准重写模板(通用版))

[6.4 关键工具类:Objects](#6.4 关键工具类:Objects)

[七、进阶知识点:equals () 与 == 的区别](#七、进阶知识点:equals () 与 == 的区别)

[7.1 核心区别](#7.1 核心区别)

[7.2 实战代码演示](#7.2 实战代码演示)

八、高频面试题全解(背会直接拿分)

[面试题 1:equals () 和 hashCode () 的作用是什么?](#面试题 1:equals () 和 hashCode () 的作用是什么?)

[面试题 2:为什么重写 equals () 必须重写 hashCode ()?](#面试题 2:为什么重写 equals () 必须重写 hashCode ()?)

[面试题 3:两个对象 equals 相等,hashCode 一定相等吗?](#面试题 3:两个对象 equals 相等,hashCode 一定相等吗?)

[面试题 4:两个对象 hashCode 相等,equals 一定相等吗?](#面试题 4:两个对象 hashCode 相等,equals 一定相等吗?)

[面试题 5:hashCode () 是唯一的吗?](#面试题 5:hashCode () 是唯一的吗?)

[面试题 6:String 类重写 equals () 和 hashCode () 了吗?](#面试题 6:String 类重写 equals () 和 hashCode () 了吗?)

九、常见误区与避坑指南

[误区 1:使用可变属性参与 hashCode () 计算](#误区 1:使用可变属性参与 hashCode () 计算)

[误区 2:重写 equals 时不遵守五大规范](#误区 2:重写 equals 时不遵守五大规范)

[误区 3:认为 hashCode () 是唯一标识](#误区 3:认为 hashCode () 是唯一标识)

[误区 4:null 对象调用 equals ()](#误区 4:null 对象调用 equals ())

十、总结:万字长文核心提炼

总结


前言

小编作为新晋码农一枚,会定期整理一些写的比较好的代码,作为自己的学习笔记,会试着做一下批注和补充,如转载或者参考他人文献会标明出处,非商用,如有侵权会删改!欢迎大家斧正和讨论!

在 Java 编程语言的体系中,java.lang.Object 是所有类的根父类 ,这意味着 Java 中的每一个对象,都天生拥有 Object 类中定义的核心方法。其中,equals()hashCode() 是两个贯穿 Java 开发全流程、支撑哈希集合核心逻辑、面试高频考察的关键方法。

对于初级开发者而言,常常会陷入三个误区:

  1. 混淆 equals()== 的使用场景;
  2. 不理解 hashCode() 的真正作用,认为它是 "无用的方法";
  3. 只重写 equals() 不重写 hashCode(),导致 HashMap、HashSet 等集合出现数据重复、查找失败、内存泄漏等隐蔽 Bug。

对于中高级开发者而言,深入理解这两个方法的底层契约、源码实现、哈希表工作原理,是写出健壮、高效、无 Bug 代码的基础,也是应对大厂面试、架构设计的必备能力。

本文将从基础定义、默认实现、核心区别、官方契约、源码解析、实战案例、常见问题、面试真题 八个维度,用万字长文 全面、深度、通俗地讲解 hashCode()equals(),让你彻底吃透这两个 Java 核心知识点。

一、溯源:Object 类中的 equals () 与 hashCode ()

所有 Java 类都隐式继承自 Object,因此我们首先看这两个方法在根类中的原始定义、实现逻辑、核心作用

1.1 equals () 方法的官方定义与默认实现

官方源码
java 复制代码
public boolean equals(Object obj) {
    return (this == obj);
}
核心解读
  1. 方法功能 :判断当前对象(this)与传入对象(obj)是否相等
  2. 默认实现 :直接使用 == 比较两个对象的内存地址
  3. 返回值
    • true:两个对象是同一个对象(内存地址完全相同);
    • false:两个对象是不同对象(内存地址不同)。
官方规范(JavaDoc)

equals() 方法在 Java 中必须遵守五大通用规范,这是所有重写该方法的类都必须遵循的准则:

  1. 自反性 :对于任何非空引用值 xx.equals(x) 必须返回 true
  2. 对称性 :对于任何非空引用值 xyx.equals(y)true 时,y.equals(x) 也必须为 true
  3. 传递性 :对于任何非空引用值 xyzx.equals(y)y.equals(z)true 时,x.equals(z) 也必须为 true
  4. 一致性 :对于任何非空引用值 xy,只要对象属性未修改,多次调用 x.equals(y) 必须始终返回相同结果;
  5. 非空性 :对于任何非空引用值 xx.equals(null) 必须返回 false

这五大规范是 equals() 方法的底层契约,违反任意一条,都会导致程序逻辑混乱。

1.2 hashCode () 方法的官方定义与默认实现

官方源码
java 复制代码
public native int hashCode();
核心解读
  1. 关键字 native :表示这是一个本地方法,底层由 C/C++ 实现,直接调用操作系统底层逻辑生成哈希值;
  2. 默认实现 :根据对象的内存地址 生成一个 int 类型的整数(哈希码);
  3. 核心作用 :为对象提供一个唯一的标识,用于哈希集合(HashMap/HashSet/HashTable) 快速定位对象的存储位置。
官方规范(JavaDoc)
  1. 同一个对象,在程序执行期间,只要没有修改 ,多次调用 hashCode() 必须返回相同的整数
  2. 如果两个对象调用 equals() 方法返回 true,那么这两个对象的 hashCode() 必须返回相同的整数
  3. 如果两个对象调用 equals() 方法返回 false,它们的 hashCode() 可以相同,也可以不同(但推荐不同,提升哈希表效率)。

二、核心对比:equals () 与 hashCode () 的本质区别

很多开发者分不清两个方法的作用,我们从设计目的、使用场景、计算逻辑、依赖关系、性能 五个维度做全方位对比

对比维度 equals() hashCode()
核心设计目的 精准判断两个对象的逻辑内容是否相等 为对象生成哈希值,实现快速分组 / 定位
底层依赖 依赖对象的属性值(重写后) 依赖对象的内存地址(默认)/ 属性值(重写后)
计算成本 高:需要逐字段比对对象属性 低:直接返回一个整数,无复杂计算
主要使用场景 所有需要判断对象相等的场景 仅用于哈希表(HashMap/HashSet 等)
返回值类型 boolean(true/false) int(32 位整数)
是否必须重写 自定义对象需要逻辑判等时必须重写 只要重写 equals,必须同时重写
核心价值 保证对象比较的准确性 保证哈希表操作的高效性

通俗比喻(最易理解)

我们把哈希集合 比作一个大型图书馆

  1. hashCode () = 图书分类号 图书馆根据分类号,把图书放到对应的书架(哈希桶)中。找书时,先看分类号,直接定位到对应书架,速度极快
  2. equals () = 图书 ISBN 编码 同一个书架上可能有很多书(哈希冲突),需要通过 ISBN 编码精准核对,找到你想要的那一本,精准无误

总结:

  • hashCode() 负责快速缩小范围
  • equals() 负责最终精准确认 。两者配合,才能让哈希集合同时拥有O (1) 时间复杂度的高效查询能力。

三、黄金契约:equals () 与 hashCode () 的三大绑定规则

这是本文最核心 的知识点,也是 Java 语言的底层强制契约任何违反契约的代码,都会导致哈希集合彻底失效

契约 1:equals () 相等 → hashCode () 必须相等

如果两个对象通过 equals() 判断为逻辑相等 ,那么它们的哈希码必须完全相同

✅ 正确逻辑:对象A.equals(对象B) = true ➡️ 对象A.hashCode() = 对象B.hashCode()

❌ 违反后果:哈希集合会把两个逻辑相等的对象 ,放到不同的哈希桶 中,导致数据重复存储、查找失败

契约 2:hashCode () 相等 → equals () 不一定相等

如果两个对象的哈希码相同,它们的 equals() 结果可以是 true,也可以是 false

这种现象叫做哈希冲突,是哈希表的正常现象。

  • 哈希冲突时,对象会挂载到同一个哈希桶的链表 / 红黑树上;
  • 最终通过 equals() 区分是否为同一个对象。

契约 3:对象不变 → hashCode () 永远不变

同一个对象,在程序运行期间,只要属性没有修改,无论调用多少次 hashCode(),返回值必须保持不变。

❌ 违反后果:将对象存入 HashMap 后,修改对象属性 → 哈希码改变 → 再也无法找到该对象 → 内存泄漏、数据丢失

四、致命问题:只重写 equals () 不重写 hashCode () 的灾难

这是 Java 开发中最经典、最隐蔽、最高发 的 Bug。我们通过实战代码演示后果,让你直观理解契约的重要性。

4.1 测试场景

自定义一个 User 类,仅重写 equals(),不重写 hashCode(),将对象存入 HashSet(底层基于 HashMap 实现)。

java 复制代码
import java.util.HashSet;

// 自定义用户类
class User {
    private int userId;

    // 构造方法
    public User(int userId) {
        this.userId = userId;
    }

    // 仅重写 equals:根据userId判断逻辑相等
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        User user = (User) obj;
        return userId == user.userId;
    }

    // 【错误】没有重写hashCode()
}

// 测试类
public class HashTest {
    public static void main(String[] args) {
        User u1 = new User(1001);
        User u2 = new User(1001);

        // 1. 判断逻辑相等
        System.out.println("u1.equals(u2) = " + u1.equals(u2)); // 输出:true

        // 2. 存入HashSet
        HashSet<User> set = new HashSet<>();
        set.add(u1);
        set.add(u2);

        // 3. 查看集合大小
        System.out.println("集合元素个数:" + set.size()); // 输出:2
    }
}

4.2 运行结果与问题分析

  1. 预期结果:u1u2 逻辑相等,HashSet 应该去重,集合大小为 1
  2. 实际结果:集合大小为 2重复存储了两个逻辑相同的对象

4.3 根本原因

  1. u1u2 重写了 equals(),返回 true
  2. 没有重写 hashCode(),使用 Object 类默认实现:根据内存地址生成哈希码
  3. u1u2 是两个不同对象,内存地址不同 → 哈希码不同;
  4. HashSet 根据哈希码将两个对象放到不同的哈希桶中;
  5. HashSet 认为两个对象是不同元素,允许重复存储。

4.4 解决方案:同时重写两个方法

修改 User 类,必须同时重写 equals()hashCode()

java 复制代码
import java.util.Objects;

class User {
    private int userId;

    public User(int userId) {
        this.userId = userId;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        User user = (User) obj;
        return userId == user.userId;
    }

    // 【正确】重写hashCode,使用与equals相同的属性
    @Override
    public int hashCode() {
        return Objects.hash(userId);
    }
}

再次运行测试代码:

  • u1.equals(u2) = true
  • u1.hashCode() = u2.hashCode()
  • 两个对象被放入同一个哈希桶
  • HashSet 去重成功,集合大小 = 1

五、源码深度解析:HashMap 如何依赖 equals () 和 hashCode ()?

HashMap 是 Java 中最常用的集合,其核心逻辑完全依赖 hashCode()equals()。我们通过 JDK 1.8 源码,解析两个方法的工作流程。

5.1 HashMap 存储 / 查找核心流程

  1. 调用 key.hashCode() 计算哈希值 → 计算数组下标 → 定位哈希桶;
  2. 如果哈希桶为空,直接存储对象;
  3. 如果哈希桶不为空,遍历链表 / 红黑树;
  4. 调用 key.equals() 精准匹配,找到则覆盖,未找到则新增节点。

5.2 JDK 1.8 HashMap 核心源码(简化版)

1. 计算哈希值
java 复制代码
static final int hash(Object key) {
    int h;
    // 调用key的hashCode()方法
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
2. 查找元素(get 方法)
java 复制代码
public V get(Object key) {
    Node<K,V> e;
    // 1. 计算hash值 2. 定位节点
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V> first;
    // 根据hash值定位哈希桶
    if ((first = tab[(n-1) & hash]) != null) {
        // 【第一步】比较hashCode
        if (first.hash == hash && 
            // 【第二步】比较equals
            (key == first.key || (key != null && key.equals(first.key))))
            return first;
    }
    return null;
}

5.3 源码核心结论

  1. hashCode () 决定对象存在哪个桶
  2. equals () 决定桶中哪个是目标对象
  3. 缺少任意一个方法,或违反契约,HashMap 都会出现逻辑错误。

六、最佳实践:如何正确重写 equals () 和 hashCode ()?

在实际开发中,自定义类必须同时重写 两个方法,且遵循统一规范

6.1 重写核心原则

  1. 两个方法必须使用完全相同的属性
  2. 不要使用可变属性参与计算(避免哈希码改变);
  3. 优先使用 JDK 提供的 Objects 工具类,避免空指针;
  4. 直接使用 IDE 自动生成(推荐,无手写错误)。

6.2 IDEA 一键生成教程

  1. 定义类的属性;
  2. 快捷键 Alt + Insert
  3. 选择 equals() and hashCode()
  4. 勾选参与比较的属性 → 自动生成标准代码。

6.3 标准重写模板(通用版)

java 复制代码
import java.util.Objects;

public class Student {
    // 成员变量
    private String name;
    private int age;
    private String studentId;

    // 构造方法
    public Student(String name, int age, String studentId) {
        this.name = name;
        this.age = age;
        this.studentId = studentId;
    }

    // 重写equals()
    @Override
    public boolean equals(Object o) {
        // 1. 自反性:同一对象直接返回true
        if (this == o) return true;
        // 2. 非空性+类型判断
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 类型强转
        Student student = (Student) o;
        // 4. 比较所有关键属性
        return age == student.age &&
                Objects.equals(name, student.name) &&
                Objects.equals(studentId, student.studentId);
    }

    // 重写hashCode():使用与equals完全相同的属性
    @Override
    public int hashCode() {
        return Objects.hash(name, age, studentId);
    }
}

6.4 关键工具类:Objects

JDK 1.7 提供的 Objects 类,是重写两个方法的神器

  1. Objects.equals(a, b):自动处理空指针,安全比较对象;
  2. Objects.hash(a, b, c):自动组合多个属性生成哈希码,一行代码搞定。

七、进阶知识点:equals () 与 == 的区别

这是面试必考题,也是开发者最容易混淆的点。

7.1 核心区别

  1. ==

    • 基本数据类型:比较值是否相等
    • 引用数据类型:比较内存地址是否相等
  2. equals()

    • 引用数据类型:默认比较内存地址,重写后比较对象内容

7.2 实战代码演示

java 复制代码
public class EqualsTest {
    public static void main(String[] args) {
        // 基本类型
        int a = 10;
        int b = 10;
        System.out.println(a == b); // true

        // 引用类型:String(重写了equals)
        String s1 = new String("Java");
        String s2 = new String("Java");
        System.out.println(s1 == s2); // false(地址不同)
        System.out.println(s1.equals(s2)); // true(内容相同)

        // 自定义对象
        Student stu1 = new Student("张三", 18, "001");
        Student stu2 = new Student("张三", 18, "001");
        System.out.println(stu1 == stu2); // false
        System.out.println(stu1.equals(stu2)); // true
    }
}

八、高频面试题全解(背会直接拿分)

面试题 1:equals () 和 hashCode () 的作用是什么?

  • equals():判断对象逻辑内容是否相等,重写后保证比较准确性;
  • hashCode():生成对象哈希码,用于哈希表快速定位,保证操作效率。

面试题 2:为什么重写 equals () 必须重写 hashCode ()?

为了遵守 Java 官方契约,保证哈希集合(HashMap/HashSet)正常工作。如果只重写 equals,会导致逻辑相等的对象哈希码不同,出现数据重复、查找失败

面试题 3:两个对象 equals 相等,hashCode 一定相等吗?

一定相等,这是 Java 强制契约。

面试题 4:两个对象 hashCode 相等,equals 一定相等吗?

不一定,这是哈希冲突,需要 equals 进一步精准判断。

面试题 5:hashCode () 是唯一的吗?

不是。不同对象可能生成相同的哈希码(哈希冲突)。

面试题 6:String 类重写 equals () 和 hashCode () 了吗?

重写了:

  • equals():比较字符串内容;
  • hashCode():根据字符串字符计算哈希码。这也是 String 适合作为 HashMap key 的原因。

九、常见误区与避坑指南

误区 1:使用可变属性参与 hashCode () 计算

❌ 错误:修改属性 → 哈希码改变 → 对象丢失;✅ 正确:使用不可变属性(如 id、uuid)参与计算。

误区 2:重写 equals 时不遵守五大规范

❌ 错误:破坏对称性、传递性,导致程序逻辑混乱;✅ 正确:使用 IDE 自动生成,保证规范。

误区 3:认为 hashCode () 是唯一标识

❌ 错误:哈希码允许重复(哈希冲突);✅ 正确:哈希码仅用于快速定位,最终判断必须用 equals。

误区 4:null 对象调用 equals ()

❌ 错误:空指针异常;✅ 正确:使用 Objects.equals() 安全比较。

十、总结:万字长文核心提炼

  1. 底层定位equals()hashCode() 是 Object 类的核心方法,是哈希集合的基石;
  2. 核心分工
    • equals()精准判等,保证数据准确性;
    • hashCode()快速定位,保证操作高效性;
  3. 黄金契约
    • equals 相等 ➡️ hashCode 必相等;
    • hashCode 相等 ➡️ equals 不一定相等;
  4. 开发铁律重写 equals 必须同时重写 hashCode,且使用相同属性;
  5. 实战技巧 :使用 IDE 自动生成 + Objects 工具类,避免手写错误;
  6. 面试核心:理解契约、源码逻辑、区别对比,轻松应对所有相关面试题。

掌握 equals()hashCode(),不仅能写出无 Bug 的 Java 代码,更能理解哈希表的底层设计思想,这是从初级开发者进阶为中高级开发者的必经之路


总结

以上就是今天要讲的内容,本文简单记录了java数据结构,仅作为一份简单的笔记使用,大家根据注释理解,您的点赞关注收藏就是对小编最大的鼓励!

相关推荐
gelald2 小时前
JVM - 运行时内存模型
java·jvm·后端
老虎06272 小时前
Java基础面试题(08)—Java(集合—HashMap的使用和实现原理红黑树)
java·开发语言
IT从业者张某某2 小时前
基于DEVC++实现一个控制台的赛车游戏-02-实现赛车游戏
开发语言·c++·游戏
HappyAcmen2 小时前
理解Python中的global与nonlocal
python
guygg882 小时前
基于数据驱动的模型预测控制电力系统机组组合优化MATLAB实现
开发语言·matlab
lly2024062 小时前
组合模式:深入理解与实际应用
开发语言
2501_908329852 小时前
C++中的备忘录模式
开发语言·c++·算法
左左右右左右摇晃2 小时前
Java笔记——JMM
java·后端·spring
Schengshuo3 小时前
Spring学习——新建module模块
java·学习·spring