当前位置: 首页 > ds >正文

Java面试-== 和 equals() 方法的区别与实现原理

请添加图片描述

👋 欢迎阅读《Java面试200问》系列博客!

🚀大家好,我是Jinkxs,一名热爱Java、深耕技术一线的开发者。在准备和参与了数十场Java面试后,我深知面试不仅是对知识的考察,更是对理解深度与表达能力的综合检验。

✨本系列将带你系统梳理Java核心技术中的高频面试题,从源码原理到实际应用,从常见陷阱到大厂真题,每一篇文章都力求深入浅出、图文并茂,帮助你在求职路上少走弯路,稳拿Offer!

🔍今天我们要聊的是:《== 和 equals() 方法的区别与实现原理》。准备好了吗?Let’s go!


🎯 引言:Java 世界的“身份证”与“长相”之争

“在Java的世界里,最像‘认人’的,不是人脸识别系统,而是——==equals()。”

想象一下:

  • 你去银行办业务,柜员问:“你是张三吗?”
  • 你回答:“是的!”
  • 柜员拿出身份证一刷:“证件号:123456” → 这是 ==,看是不是同一个人(同一个对象)。
  • 柜员再看看你的脸:“嗯,长得确实像张三” → 这是 equals(),看是不是长得一样(内容相等)。

但问题来了:

  • 如果你是张三的双胞胎弟弟,脸长得一模一样,但身份证不同,柜员认不认?
  • 如果你是张三,但整容了,脸变了,身份证还是那个,柜员还认你吗?

今天,我们就来扒一扒这两位“认人专家”——==equals()——的底细,看看它们如何在Java世界里“认人”。


📚 目录导航(别走丢了)

  1. ==:身份证号比对,看是不是“同一个人”
  2. equals():长相比对,看是不是“长得一样”
  3. ==equals() 的默认行为揭秘
  4. 为什么 String==equals() 有时一样,有时不一样?
  5. 自定义类的 equals() 正确写法(含10个坑)
  6. hashCode()equals() 的“黄金搭档”关系
  7. 面试常见陷阱大揭秘
  8. 最佳实践与使用场景总结

1. ==:身份证号比对,看是不是“同一个人”

== 是Java中的相等运算符,它比较的是两个变量的

但这个“值”是什么,取决于变量的类型:

  • 对于基本类型:比较的是实际的数值
  • 对于引用类型:比较的是对象的内存地址(引用)

✅ 基本类型:数值比对

int a = 100;
int b = 100;
System.out.println(a == b); // ✅ true,数值相等double x = 3.14;
double y = 3.14;
System.out.println(x == y); // ✅ true

✅ 基本类型直接比“值”,简单粗暴。


✅ 引用类型:引用(内存地址)比对

String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2); // ❌ false!

❓ 为什么?虽然内容都是 “Hello”,但 s1s2两个不同的对象,内存地址不同。

String s3 = s1;
System.out.println(s1 == s3); // ✅ true,s3 和 s1 指向同一个对象

== 只关心“是不是同一个对象”,不关心“内容是否一样”。


📊 == 比较机制图解

s1 → [对象1: "Hello"]  
s2 → [对象2: "Hello"]  
s3 → s1 → [对象1: "Hello"]s1 == s2 → ❌ 地址不同  
s1 == s3 → ✅ 地址相同

✅ 记住:== 看的是“身份证号”,不是“长相”。


2. equals():长相比对,看是不是“长得一样”

equals()Object 类的一个方法,用于比较两个对象的“逻辑相等性”

✅ 默认行为:等价于 ==

Object 类中的 equals() 方法默认实现就是用 == 比较引用:

public boolean equals(Object obj) {return (this == obj);
}

所以,对于没有重写 equals() 的类,equals()== 效果一样。


✅ 重写 equals():自定义“长相”标准

但很多类(如 StringIntegerList)都重写了 equals(),让它比较内容而不是引用。

示例:Stringequals()
String s1 = new String("Java");
String s2 = new String("Java");System.out.println(s1 == s2);        // ❌ false,不同对象
System.out.println(s1.equals(s2));   // ✅ true,内容相同

Stringequals() 比较的是字符序列是否相同


3. ==equals() 的默认行为揭秘

✅ 代码示例:未重写 equals() 的类

class Person {String name;int age;public Person(String name, int age) {this.name = name;this.age = age;}
}public class DefaultEquals {public static void main(String[] args) {Person p1 = new Person("张三", 25);Person p2 = new Person("张三", 25);System.out.println(p1 == p2);        // ❌ false,不同对象System.out.println(p1.equals(p2));   // ❌ false,因为 equals() 默认用 == 比较}
}

❓ 问题来了:两个 Person 对象,名字年龄都一样,按理说“长得一样”,但 equals() 却说不相等!

原因Person 类没有重写 equals(),用的是 Object 的默认实现,也就是 ==


4. 为什么 String==equals() 有时一样,有时不一样?

这是面试必问神题!

✅ 代码示例:字符串常量池的“魔法”

String a = "Hello";
String b = "Hello";
System.out.println(a == b);        // ✅ true
System.out.println(a.equals(b));   // ✅ trueString c = new String("Hello");
String d = new String("Hello");
System.out.println(c == d);        // ❌ false
System.out.println(c.equals(d));   // ✅ true

❓ 为什么 a == btrue?它们不是两个变量吗?

🔍 答案:字符串常量池(String Pool)!

当你用双引号创建字符串时,JVM会先检查字符串常量池:

  • 如果池中已有相同内容的字符串,就返回同一个引用
  • 如果没有,就创建新字符串并放入池中。

所以:

String a = "Hello"; // 池中无,创建 "Hello" 并放入池,a 指向它
String b = "Hello"; // 池中有,b 直接指向同一个 "Hello"
a == b → true      // 同一个对象

new String("Hello")强制创建新对象,不管池中有没有。

✅ 记住:== 比较引用,equals() 比较内容
字符串常量池让 == 有时“看起来”像在比较内容,但本质没变。


📊 字符串常量池机制图解

字符串常量池:"Hello" → [内存地址 0x100]a = "Hello" → 指向 0x100
b = "Hello" → 指向 0x100
a == b → ✅ truec = new String("Hello") → 在堆上创建新对象 [0x200],内容 "Hello"
d = new String("Hello") → 在堆上创建新对象 [0x300],内容 "Hello"
c == d → ❌ false

5. 自定义类的 equals() 正确写法(含10个坑)

想让你的类能正确比较“长相”,必须重写 equals()

✅ 正确写法模板

@Override
public boolean equals(Object obj) {// 1. 自反性:自己和自己比较if (this == obj) return true;// 2. 空值检查if (obj == null) return false;// 3. 类型检查if (getClass() != obj.getClass()) return false;// 或者用 instanceof(见坑4)// 4. 类型转换Person other = (Person) obj;// 5. 逐个字段比较return age == other.age && Objects.equals(name, other.name);// 用 Objects.equals() 安全处理 null
}

❌ 坑1:忘记 @Override 注解

// ❌ 错误:方法签名写错了,其实是新方法,不是重写
public boolean equals(Person p) {return this.name.equals(p.name) && this.age == p.age;
}// ✅ 正确:加上 @Override,让编译器帮你检查
@Override
public boolean equals(Object obj) { ... }

@Override 能防止你写错方法名或参数类型。


❌ 坑2:不检查 null

@Override
public boolean equals(Object obj) {Person other = (Person) obj;return name.equals(other.name) && age == other.age;// ❌ 如果 obj 为 null,这里抛 NPE!
}

✅ 必须先检查 obj == null


❌ 坑3:用 instanceof 不当,破坏对称性

class Person {String name;int age;// ...@Overridepublic boolean equals(Object obj) {if (obj instanceof Person) {Person p = (Person) obj;return name.equals(p.name) && age == p.age;}return false;}
}class Student extends Person {String studentId;// ...@Overridepublic boolean equals(Object obj) {if (obj instanceof Student) {Student s = (Student) obj;return super.equals(obj) && studentId.equals(s.studentId);}return false;}
}

❓ 问题:Person p = new Person("张三", 25);
Student s = new Student("张三", 25, "S001");
p.equals(s)true(因为 s 是 Person)
s.equals(p)false(因为 p 不是 Student)
不对称!

✅ 解决方案:用 getClass() 比较,确保类型完全相同。


❌ 坑4:浮点数比较用 ==

class Point {double x, y;// ...@Overridepublic boolean equals(Object obj) {Point p = (Point) obj;return x == p.x && y == p.y; // ❌ 浮点数精度问题!}
}

✅ 正确做法:用 Double.compare() 或允许误差。

return Double.compare(x, p.x) == 0 && Double.compare(y, p.y) == 0;
// 或
return Math.abs(x - p.x) < 1e-9 && Math.abs(y - p.y) < 1e-9;

❌ 坑5:数组字段比较不当

class Data {int[] values;// ...@Overridepublic boolean equals(Object obj) {Data d = (Data) obj;return Arrays.equals(values, d.values); // ✅ 正确// return values == d.values; // ❌ 错误,比较引用}
}

✅ 用 Arrays.equals() 比较数组内容。


❌ 坑6:集合字段比较不当

class Group {List<String> members;// ...@Overridepublic boolean equals(Object obj) {Group g = (Group) obj;return members.equals(g.members); // ✅ 正确,List 重写了 equals()}
}

✅ 大多数集合类都正确重写了 equals()


❌ 坑7:不满足“传递性”

// ❌ 错误示例
if (name != null ? name.equals(other.name) : other.name == null) {// 只比较了 name,忽略了 agereturn true;
}
return false;

✅ 必须比较所有相关字段,确保 a.equals(b)b.equals(c) 蕴含 a.equals(c)


❌ 坑8:可变字段作为 equals 依据

class MutablePerson {private String name; // 可变@Overridepublic boolean equals(Object obj) {MutablePerson p = (MutablePerson) obj;return name.equals(p.name);}
}

❓ 问题:如果 name 被修改,equals() 结果会变,可能导致 HashSet 中的对象“丢失”。

✅ 建议:用不可变字段(如 final)作为 equals() 依据。


❌ 坑9:性能问题——不必要的复杂计算

@Override
public boolean equals(Object obj) {// ❌ 错误:每次比较都计算哈希值return this.hashCode() == obj.hashCode();
}

equals() 应该快速、简单。复杂计算放 hashCode()


❌ 坑10:忘记 hashCode() 的约定

这是最大的坑!见下一节。


6. hashCode()equals() 的“黄金搭档”关系

hashCode()equals() 是一对“黄金搭档”,必须同时重写,且遵守一个铁律:

如果两个对象用 equals() 比较相等,那么它们的 hashCode() 必须相等。

✅ 为什么?

因为 HashSetHashMap 等集合依赖 hashCode() 来快速定位对象。

示例:不重写 hashCode() 的灾难
class Person {String name;int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic boolean equals(Object obj) {if (this == obj) return true;if (obj == null || getClass() != obj.getClass()) return false;Person p = (Person) obj;return age == p.age && Objects.equals(name, p.name);}// ❌ 忘记重写 hashCode()!
}public class HashCodeProblem {public static void main(String[] args) {Person p1 = new Person("张三", 25);Person p2 = new Person("张三", 25);System.out.println(p1.equals(p2)); // ✅ trueSet<Person> set = new HashSet<>();set.add(p1);System.out.println(set.contains(p2)); // ❌ false!}
}

❓ 为什么 contains(p2) 返回 false
因为 p1p2 虽然 equals() 相等,但 hashCode() 不同(继承自 Object),导致 HashSet 认为它们是不同的“桶”,根本不会去比较。


✅ 正确重写 hashCode()

@Override
public int hashCode() {return Objects.hash(name, age);// 或手动计算// int result = name != null ? name.hashCode() : 0;// result = 31 * result + age;// return result;
}

Objects.hash() 是最简单安全的方式。


📊 HashSet 工作原理图解

hashCode() → 决定“桶”(Bucket)
equals()   → 在桶内比较“是不是同一个”Person p1: hashCode=123 → 桶A
Person p2: hashCode=456 → 桶B  (即使 equals 相等,也不在同一个桶)重写后:
Person p1: hashCode=789 → 桶C
Person p2: hashCode=789 → 桶C
然后用 equals() 比较 → 相等 → contains 返回 true

✅ 记住:equals() 相等 ⇒ hashCode() 必须相等
反过来不成立(哈希冲突)。


7. 面试常见陷阱大揭秘

❓ 面试题1:==equals() 的区别?

✅ 答:

  • ==:基本类型比数值,引用类型比内存地址
  • equals():默认行为同 ==,但通常被重写为比较逻辑内容

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

✅ 答:为了满足 HashSet/HashMap 的契约。
如果 equals() 相等但 hashCode() 不同,对象在哈希集合中无法被正确找到。


❓ 面试题3:String== 什么时候为 true

✅ 答:当两个 String 变量指向同一个对象时,包括:

  • 字符串常量池中的同一个字符串。
  • 同一个 new String() 对象的引用。
  • intern() 返回的字符串。

❓ 面试题4:如何正确重写 equals()

✅ 答:遵循以下步骤:

  1. == 检查自反性。
  2. 检查 null
  3. getClass()instanceof 检查类型。
  4. 转换类型。
  5. 逐个字段比较(注意 null、浮点数、数组等)。
  6. 同时重写 hashCode()

❓ 面试题5:Integer== 比较有什么陷阱?

✅ 答:-128127 之间的 Integer 有缓存,== 可能为 true;超出范围则为 false
所以比较值应该用 equals()

Integer a = 127, b = 127; System.out.println(a == b); // true
Integer c = 128, d = 128; System.out.println(c == d); // false

8. 最佳实践与使用场景总结

场景推荐做法原因
比较基本类型使用 ==直接比较数值
比较引用类型是否为同一对象使用 ==检查内存地址
比较对象内容是否相等使用 equals()逻辑相等性
自定义类正确重写 equals()hashCode()确保在集合中正常工作
比较字符串内容使用 equals()安全、可靠
字符串常量比较可用 ==(但不推荐)因为常量池,但行为不稳定
性能敏感代码避免不必要的 equals() 调用方法调用有开销

✅ 黄金法则:

  1. == 看“身份证”,equals() 看“长相”。
  2. 重写 equals(),必重写 hashCode()
  3. 比较内容用 equals(),别用 ==
  4. 善用 Objects.equals()Objects.hash()

📈 附录:== vs equals() 速查表

比较类型==equals()
基本类型比较数值❌ 不适用
引用类型比较内存地址比较逻辑内容(需重写)
String 字面量相同true(常量池)true
String new 出来相同内容falsetrue
Integer 127true(缓存)true
Integer 128falsetrue
null 比较null == nulltruenull.equals()NPE

💡 记住:equals() 是方法,可以被重写;== 是运算符,行为固定。
理解它们,你就能在Java世界里“认人”不迷路!


🎯 总结一下:

本文深入探讨了《== 和 equals() 方法的区别与实现原理》,从原理到实践,解析了面试中常见的考察点和易错陷阱。掌握这些内容,不仅能应对面试官的连环追问,更能提升你在实际开发中的技术判断力。

🔗 下期预告:我们将继续深入Java面试核心,带你解锁《** hashCode() 和 equals() 为什么必须成对重写?**》 的关键知识点,记得关注不迷路!

💬 互动时间:你在面试中遇到过类似问题吗?或者对本文内容有疑问?欢迎在评论区留言交流,我会一一回复!

如果你觉得这篇文章对你有帮助,别忘了 点赞 + 收藏 + 转发,让更多小伙伴一起进步!我们下一篇见 👋

http://www.xdnf.cn/news/18659.html

相关文章:

  • Unreal Engine UE_LOG
  • 电脑芯片大的32位与64位指的是什么
  • 【数据结构】B+ 树——高度近似于菌丝网络——详细解说与其 C 代码实现
  • 面向RF设计人员的微带贴片天线计算器
  • AI领域的语义空间是什么?
  • ES6变量与解构:let、const与模板字符串全解析
  • 「越短越合法」型滑动窗口
  • 解释一下,Linux,shell,Vmware,Ubuntu,以及Linux命令和shell命令的区别
  • 111、【OS】【Nuttx】【周边】效果呈现方案解析:-print0 选项
  • Linux操作系统编程——网络
  • 第二阶段WinFrom-6:文件对话框,对象的本地保存,序列化与反序列化,CSV文件操作,INI文件读写
  • 08.21总结
  • Claude Code 三类.md文件
  • Day2--HOT100--283. 移动零,11. 盛最多水的容器,15. 三数之和
  • PCB电路设计学习2 元件原理图封装的添加 手工设计元件封装
  • 大型前端项目如何实现css 隔离:利用浏览器原生的 Shadow DOM 完全隔离 DOM 结构与样式...
  • Linux 下的网络编程
  • 学习嵌入式的第二十四天——数据结构——队列和树
  • Git 提交除某个文件外的其他所有文件
  • 微信开发者工具:更改 AppID 失败
  • 嵌入式-EXTI的工作原理和按钮实验-Day19
  • 我从零开始学习C语言(13)- 循环语句 PART2
  • QT-窗口类部件
  • K8S高可用集群
  • K8s的相关知识总结
  • 如何理解面向过程和面向对象,举例说明一下?
  • Qt5 的跨平台开发详细讲解
  • 计算机毕设选题推荐 基于Spark的家庭能源消耗智能分析与可视化系统 基于机器学习的家庭能源消耗预测与可视化系统源码
  • 告别第三方流氓工具,如何实现纯净系统维护
  • DIC技术极端环境高温案例分享——从1600℃的锆合金力学性能测试到3000℃变形测试的DIC测量