Java学习——equals ()、hashCode () 核心区别与底层实现
·
目录
坑点 2:重写 equals () 却不重写 hashCode ()
2. 为什么重写 equals () 必须重写 hashCode ()?
3. hashCode 相等,equals 一定相等吗?反过来呢?
4. Object 类的 equals 和 hashCode 默认实现是什么?
5. 如何正确重写 equals () 和 hashCode ()?
适用场景:学生管理系统、SpringBoot 后端项目、MyBatis 实体类
一、核心定义与设计思想
1. == (相等运算符)
- 定义:Java 原生二元运算符,无重写可能,作用分两种场景:
- 基本数据类型:比较变量存储的数值 / 值是否相等;
- 引用数据类型:比较对象在堆内存的地址是否相等(是否为同一个对象)。
- 设计思想:快速、底层的相等判断,仅做物理层面的比较,不关心业务逻辑。
2. equals () (对象相等方法)
- 定义:
java.lang.Object类中的实例方法,可被子类重写:- 默认实现:和
==完全一致,比较对象内存地址; - 重写后:比较对象的业务内容 / 属性是否相等(如 String、Integer 均重写)。
- 默认实现:和
- 设计思想:逻辑层面的相等判断,满足业务场景中「内容相同即相等」的需求。
3. hashCode () (哈希码方法)
- 定义:
java.lang.Object类中的native 本地方法(C++ 实现),返回一个int类型的哈希值。 - 设计思想:为哈希表(HashMap/HashSet/HashTable)服务,通过哈希值快速定位对象存储位置,提升查找效率。
4. 三者核心契约(面试必考)
- 若两个对象
equals()结果为true,则它们的hashCode()必须相等; - 若两个对象
hashCode()相等,equals()不一定相等(哈希冲突); - 重写
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 () 的核心区别?
标准答案:
==是运算符,equals()是 Object 方法;- 基本类型:
==比较值,equals()不能用于基本类型; - 引用类型:
==比较内存地址,equals()默认比地址,重写后比内容; ==无法重写,equals()可以重写。
2. 为什么重写 equals () 必须重写 hashCode ()?
标准答案:
为了遵守 Java 哈希契约,保证哈希表(HashMap/HashSet)正常工作。
如果只重写 equals 不重写 hashCode,会导致:
- 内容相等的对象,hashCode 不同;
- 哈希表会将它们存到不同位置,无法去重、查找失效。
3. hashCode 相等,equals 一定相等吗?反过来呢?
标准答案:
equals() == true→hashCode() 必须相等(契约强制);hashCode() 相等→equals() 不一定相等(哈希冲突,不同对象可能算出相同哈希值)。
4. Object 类的 equals 和 hashCode 默认实现是什么?
标准答案:
equals():直接用==比较对象内存地址;hashCode():native 方法,返回对象内存地址转换的整数。
5. 如何正确重写 equals () 和 hashCode ()?
标准答案:
- 先比较对象地址,再判断类型和非空;
- 比较核心业务字段(如 id);
- hashCode 基于 equals 用到的字段计算;
- 推荐用 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
}
问题:
- List 去重失效,无法判断两个学生是否为同一人;
- HashMap 以 Student 为键时,查找、删除失效;
- 违反企业开发规范,代码可读性差。
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. 改造好处
- 哈希表正常工作:HashSet 去重、HashMap 查找 / 删除精准;
- 业务逻辑准确:根据业务唯一标识判断对象相等;
- 符合企业规范:Java 后端开发中,POJO 实体类必须重写这两个方法;
- 兼容框架:MyBatis、Spring 等框架依赖这两个方法做对象匹配。
总结
- 核心区分:
==比地址 / 值,equals()比逻辑内容,hashCode()服务哈希表; - 底层关键:equals 重写后比属性,hashCode 重写后按属性计算;
- 黄金契约:equals 为 true → hashCode 必相等,重写前者必重写后者;
- 实战规范:引用类型比较用 equals,判空用 Objects.equals,实体类必重写两个方法。
更多推荐



所有评论(0)