目录

一、核心定义与设计思想

1. == (相等运算符)

2. equals () (对象相等方法)

3. hashCode () (哈希码方法)

4. 三者核心契约(面试必考)

二、底层实现原理(含 JDK 源码分析)

1. == 底层原理

2. equals () 源码分析

(1)Object 类默认实现(未重写)

(2)String 重写后的 equals ()(最常用)

(3)Integer 重写后的 equals ()

3. hashCode () 源码分析

(1)Object 类默认实现(native 方法)

(2)String 重写后的 hashCode ()

(3)Integer 重写后的 hashCode ()

三、代码示例

1. 基础用法全覆盖示例

四、高频踩坑点与避坑方案

坑点 1:用 == 比较引用类型的内容(最常错)

坑点 2:重写 equals () 却不重写 hashCode ()

坑点 3:null 调用 equals () 触发空指针

坑点 4:包装类用 == 比较,缓存机制干扰判断

坑点 5:可变对象重写 hashCode,导致哈希表失效

坑点 6:String 常量池误区

五、面试高频考点与标准答案

1. == 和 equals () 的核心区别?

2. 为什么重写 equals () 必须重写 hashCode ()?

3. hashCode 相等,equals 一定相等吗?反过来呢?

4. Object 类的 equals 和 hashCode 默认实现是什么?

5. 如何正确重写 equals () 和 hashCode ()?

6. Objects.equals (a,b) 优势?

六、项目改造 / 落地记录

适用场景:学生管理系统、SpringBoot 后端项目、MyBatis 实体类

1. 改造前(错误用法)

2. 改造后(标准企业用法)

3. 改造好处

总结


一、核心定义与设计思想

1. == (相等运算符)

  • 定义​:Java 原生二元运算符,​无重写可能​,作用分两种场景:
    1. 基本数据类型:比较​变量存储的数值 / 值是否相等​;
    2. 引用数据类型:比较​对象在堆内存的地址是否相等​(是否为同一个对象)。
  • 设计思想​:​快速、底层的相等判断​,仅做物理层面的比较,不关心业务逻辑。

2. equals () (对象相等方法)

  • 定义​:java.lang.Object 类中的​实例方法​,​可被子类重写​:
    1. 默认实现:和 == 完全一致,比较对象内存地址;
    2. 重写后:比较​对象的业务内容 / 属性是否相等​(如 String、Integer 均重写)。
  • 设计思想​:​逻辑层面的相等判断​,满足业务场景中「内容相同即相等」的需求。

3. hashCode () (哈希码方法)

  • 定义​:java.lang.Object 类中的​native 本地方法​(C++ 实现),返回一个 int 类型的哈希值。
  • 设计思想​:​为哈希表(HashMap/HashSet/HashTable)服务​,通过哈希值快速定位对象存储位置,提升查找效率。

4. 三者核心契约(面试必考)

  1. 若两个对象 equals() 结果为 true,则它们的 hashCode()必须相等​;
  2. 若两个对象 hashCode() 相等,equals()不一定相等​(哈希冲突);
  3. 重写 equals() 时,​**必须重写 hashCode()**​,否则哈希表失效。

二、底层实现原理(含 JDK 源码分析)

1. == 底层原理

  • 基本类型:直接比较栈内存中存储的​二进制数值​,无任何额外逻辑;
  • 引用类型:比较栈内存中存储的​对象引用地址​(指向堆内存的地址),JVM 底层直接读取地址值比较。

2. equals () 源码分析

(1)Object 类默认实现(未重写)
public boolean equals(Object obj) {
    // 直接用 == 比较对象地址,和 == 完全等价
    return (this == obj);
}
(2)String 重写后的 equals ()(最常用)
public boolean equals(Object anObject) {
    // 1. 先比较地址,相同直接返回true
    if (this == anObject) {
        return true;
    }
    // 2. 判断是否为String类型
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // 3. 先比较长度,不同直接返回false
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 4. 逐字符比较内容
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}
(3)Integer 重写后的 equals ()
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        // 直接比较包装类的数值
        return value == ((Integer)obj).intValue();
    }
    return false;
}

3. hashCode () 源码分析

(1)Object 类默认实现(native 方法)
public native int hashCode();
  • 底层:返回​对象内存地址转换的整数​,不同对象默认 hashCode 不同。
(2)String 重写后的 hashCode ()
public int hashCode() {
    int h = hash; // 缓存哈希值
    if (h == 0 && value.length > 0) {
        char val[] = value;
        // 经典哈希算法:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
(3)Integer 重写后的 hashCode ()
@Override
public int hashCode() {
    // 直接返回数值本身
    return Integer.hashCode(value);
}

三、代码示例

1. 基础用法全覆盖示例

import java.util.HashSet;
import java.util.Objects;

public class EqualsAndHashCodeTest {
    public static void main(String[] args) {
        // ===================== 1. == 用法 =====================
        // 基本类型:比较值
        int a = 10, b = 10;
        System.out.println(a == b); // true

        // 引用类型:比较地址
        String s1 = new String("abc");
        String s2 = new String("abc");
        System.out.println(s1 == s2); // false(两个不同对象)

        // ===================== 2. equals() 用法 =====================
        // 重写后:比较内容
        System.out.println(s1.equals(s2)); // true

        // ===================== 3. hashCode() 用法 =====================
        // 内容相同,hashCode相同
        System.out.println(s1.hashCode() == s2.hashCode()); // true

        // ===================== 4. 自定义类:未重写 vs 重写 =====================
        // 未重写equals/hashCode
        User user1 = new User(1, "张三");
        User user2 = new User(1, "张三");
        System.out.println(user1.equals(user2)); // false(默认比地址)
        System.out.println(user1.hashCode() == user2.hashCode()); // false

        // 存入HashSet:会存两个对象(因为hashCode不同)
        HashSet<User> set = new HashSet<>();
        set.add(user1);
        set.add(user2);
        System.out.println(set.size()); // 2(不符合业务预期)
    }
}

// 自定义实体类
class User {
    private Integer id;
    private String name;

    public User(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    // 👇 重写equals:根据id判断是否相等
    @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(id, user.id);
    }

    // 👇 重写hashCode:和equals保持一致
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

四、高频踩坑点与避坑方案

坑点 1:用 == 比较引用类型的内容(最常错)

  • 问题代码:

    String s1 = new String("java");
    String s2 = new String("java");
    System.out.println(s1 == s2); // false(想比较内容,却比了地址)
  • 避坑方案:​**引用类型比较内容必须用 equals ()**​。

坑点 2:重写 equals () 却不重写 hashCode ()

  • 问题:HashMap/HashSet 无法正确去重、查找对象;
  • 避坑方案:​重写 equals 必须同步重写 hashCode​,遵守 Java 契约。

坑点 3:null 调用 equals () 触发空指针

  • 问题代码:

String s = null;
s.equals("abc"); // 空指针异常
  • 避坑方案:用常量在前"abc".equals(s),或用 Objects.equals(a,b)(JDK7+,自动判空)。

坑点 4:包装类用 == 比较,缓存机制干扰判断

  • 问题代码:

    Integer a = 200;
    Integer b = 200;
    System.out.println(a == b); // false(超出缓存范围,比地址)
  • 避坑方案:​**所有包装类比较数值用 equals ()**​。

坑点 5:可变对象重写 hashCode,导致哈希表失效

  • 问题:修改对象属性后,hashCode 改变,HashMap 找不到该对象;
  • 避坑方案:​哈希表的键必须用不可变对象​(如 String、Integer)。

坑点 6:String 常量池误区

  • 问题代码:

    String s1 = "abc";
    String s2 = "abc";
    System.out.println(s1 == s2); // true(常量池复用),但不要依赖此特性
  • 避坑方案:​统一用 equals () 比较字符串​,不要用 ==。


五、面试高频考点与标准答案

1. == 和 equals () 的核心区别?

标准答案​:

  1. == 是运算符,equals() 是 Object 方法;
  2. 基本类型:== 比较值,equals() 不能用于基本类型;
  3. 引用类型:== 比较内存地址,equals() 默认比地址,重写后比内容;
  4. == 无法重写,equals() 可以重写。

2. 为什么重写 equals () 必须重写 hashCode ()?

标准答案​:

为了遵守 Java 哈希契约,保证​哈希表(HashMap/HashSet)正常工作​。

如果只重写 equals 不重写 hashCode,会导致:

  • 内容相等的对象,hashCode 不同;
  • 哈希表会将它们存到不同位置,无法去重、查找失效。

3. hashCode 相等,equals 一定相等吗?反过来呢?

标准答案​:

  1. equals() == true → hashCode() 必须相等(契约强制);
  2. hashCode() 相等 → equals() 不一定相等(哈希冲突,不同对象可能算出相同哈希值)。

4. Object 类的 equals 和 hashCode 默认实现是什么?

标准答案​:

  • equals():直接用 == 比较对象内存地址;
  • hashCode():native 方法,返回对象内存地址转换的整数。

5. 如何正确重写 equals () 和 hashCode ()?

标准答案​:

  1. 先比较对象地址,再判断类型和非空;
  2. 比较​核心业务字段​(如 id);
  3. hashCode 基于 equals 用到的字段计算;
  4. 推荐用 IDEA 自动生成(Objects.hash()/Objects.equals())。

6. Objects.equals (a,b) 优势?

标准答案​:

JDK7+ 工具方法,​自动判空​,避免空指针异常,是企业开发标准用法。


六、项目改造 / 落地记录

适用场景:学生管理系统、SpringBoot 后端项目、MyBatis 实体类

1. 改造前(错误用法)

// 实体类:未重写 equals() 和 hashCode()
public class Student {
    private Long id;
    private String name;
    // getter/setter
}

问题​:

  1. List 去重失效,无法判断两个学生是否为同一人;
  2. HashMap 以 Student 为键时,查找、删除失效;
  3. 违反企业开发规范,代码可读性差。

2. 改造后(标准企业用法)

// 实体类:重写 equals() 和 hashCode()(基于唯一标识id)
public class Student {
    private Long id;
    private String name;

    // 👇 IDEA自动生成,标准写法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(id, student.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

3. 改造好处

  1. 哈希表正常工作​:HashSet 去重、HashMap 查找 / 删除精准;
  2. 业务逻辑准确​:根据业务唯一标识判断对象相等;
  3. 符合企业规范​:Java 后端开发中,​POJO 实体类必须重写这两个方法​;
  4. 兼容框架​:MyBatis、Spring 等框架依赖这两个方法做对象匹配。

总结

  1. 核心区分​:== 比地址 / 值,equals() 比逻辑内容,hashCode() 服务哈希表;
  2. 底层关键​:equals 重写后比属性,hashCode 重写后按属性计算;
  3. 黄金契约​:equals 为 true → hashCode 必相等,重写前者必重写后者;
  4. 实战规范​:引用类型比较用 equals,判空用 Objects.equals,实体类必重写两个方法。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐