笔者去面试的时候,面试官冷不丁地问"Java中两个对象怎么判断是不是同一个",第一反应就是答:用
equals()。这个回答不能说错,那还需要重写什么方法,为什么,很容易被继续追问到发懵。因为面试官真正想问的是:你说的"同一个",到底是地址一样、内容一样,还是放进HashMap里也能被当成同一个 key?
一句话结论: == 像是在问"是不是同一张身份证原件",equals() 像是在问"身份证信息是不是同一个人",hashCode() 像是在问"先去哪个柜子里找这个人",三者回答的是不同层次的问题。
这篇不先堆术语。我们先用自己的话把这道题讲顺,再补一点源码和 Android 里的实际场景。你只要记住一个方向:判断对象相同,先问清楚你到底想判断哪种相同。
1. 面试官问"相同",其实可能问三件事
比如有两个 User:
java
User a = new User(1, "张三");
User b = new User(1, "张三");
你说它俩相同吗?
这个问题要看你站在哪个角度:
| 你想问什么 | 怎么去理解 | Java 里常用什么 |
|---|---|---|
| 是不是同一个对象 | 是不是同一个人站在这里 | a == b |
| 内容是不是一样 | 两张表上的姓名、id 是不是一样 | a.equals(b) |
| 放进集合能不能当成同一个 | 通讯录里会不会重复存两份 | equals() 加 hashCode() |

所以面试时别急着说"用 equals"。更稳的开头是:
要看你说的相同是哪种。如果是判断是不是同一个对象,用
==;如果是判断业务内容是不是一样,用equals();如果这个对象要放进HashMap或HashSet,还得保证hashCode()和equals()是配套的。
这句话一说,面试官基本就知道你不是只背过 API。
2. ==:看的是"是不是同一个对象"
先看最普通的例子:
java
User a = new User(1, "张三");
User b = new User(1, "张三");
User c = a;
System.out.println(a == b); // false
System.out.println(a == c); // true
为什么 a == b 是 false?
因为 new 了两次,就像打印了两张内容一样的表。表上的内容一样,但它们不是同一张纸。
为什么 a == c 是 true?
因为 c = a,就像 a 和 c 都指着同一张纸。

一句话:
==不关心对象里面写了什么,只关心左右两边是不是指向同一个对象。
3. equals():看的是"你怎么定义内容一样"
那 equals() 呢?
很多人会说:equals() 比较内容。
这句话只说对了一半。更准确地说:
equals()比较什么,取决于这个类有没有重写,以及它重写时怎么写。
默认情况下,普通类继承的是 Object.equals()。它的逻辑非常简单:
java
public boolean equals(Object obj) {
return this == obj;
}
也就是说,普通类如果不重写 equals(),那 equals() 和 == 几乎一样,还是在问"是不是同一个对象"。
比如:
java
class User {
int id;
String name;
User(int id, String name) {
this.id = id;
this.name = name;
}
}
User a = new User(1, "张三");
User b = new User(1, "张三");
System.out.println(a.equals(b)); // false
你看,内容一样,但结果还是 false。因为 User 没有告诉 Java:"我认为 id 一样就算同一个用户。"
4. String.equals() 为什么能比较文字内容
String 就不一样了。它自己重写了 equals()。
java
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
a == b 是 false,因为这是两个对象。
a.equals(b) 是 true,因为 String.equals() 比较的是里面的字符是不是一样。
你可以把 String.equals() 想成这样:
java
// 简化理解,不是逐行源码
if (两个引用就是同一个对象) {
return true;
}
if (对方不是 String) {
return false;
}
return 一个字符一个字符比较;
所以不要背"equals() 就是比较内容"。应该背这个:
equals()默认不一定比较内容。像String这种类是因为它重写了equals(),所以才比较内容。
5. 字符串和包装类为什么总喜欢出陷阱题
字符串常量池
java
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true
这怎么理解?
"hello" 这种字面量会放到字符串常量池里。s1 和 s2 指向同一个池子里的字符串,所以 s1 == s2 是 true。
new String("hello") 是重新创建了一个对象,所以 s1 == s3 是 false。
但它们文字内容一样,所以 s1.equals(s3) 是 true。
Integer 缓存
java
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b); // 通常是 true
System.out.println(c == d); // 通常是 false
这个坑更容易让人误会。常见 JDK 会缓存 -128 到 127 的 Integer 对象。127 命中了缓存,所以两个变量可能指向同一个对象;128 通常没有命中,就可能是两个对象。
但结论不是"Integer 小于 128 就用 =="。结论应该是:
包装类型判断数值相等,别依赖
==。要么拆成基本类型比较,要么用equals()。
6. 如果你想让两个 User 内容一样就算相同,该怎么写
假设业务里规定:只要 id 一样,就认为是同一个用户。
那你就要在 equals() 里写清楚:
java
public final class User {
private final long id;
private final String name;
public User(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof User)) {
return false;
}
User other = (User) obj;
return this.id == other.id;
}
@Override
public int hashCode() {
return Long.hashCode(id);
}
}
我们来拆解一下:
text
如果它俩本来就是同一个对象,直接返回 true。
如果对方根本不是 User,直接返回 false。
如果对方也是 User,那就比较 id。
id 一样,就当作同一个用户。
这里故意只比较 id,没有比较 name。因为很多业务里,用户改昵称之后,他还是同一个用户。
这就是面试官喜欢追问的地方:
你不是在写一个死板公式,你是在定义"什么叫同一个业务对象"。
7. 为什么写了 equals() 还要写 hashCode()
这一段是很多人真正掉坑的地方。
先想一个生活例子:
你去快递柜取快递。系统先告诉你:去 3 号柜。你去了 3 号柜,再看里面是不是你的包裹。
hashCode() 就像"先告诉你去哪个柜子找"。
equals() 就像"到柜子里以后确认是不是你的包裹"。

所以规则是:
text
如果两个对象 equals 是 true,
那么它们的 hashCode 必须一样。
反过来不一定:
text
两个对象 hashCode 一样,
equals 不一定是 true。
因为不同包裹可能刚好被分到了同一个柜子,这叫 hash 冲突。
8. 只写 equals() 不写 hashCode() 会发生什么
看这个错误写法:
java
public final class User {
private final long id;
public User(long id) {
this.id = id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof User)) return false;
return this.id == ((User) obj).id;
}
}
你觉得下面集合里有几个元素?
java
Set<User> users = new HashSet<>();
users.add(new User(1));
users.add(new User(1));
System.out.println(users.size());
你业务上会觉得应该是 1。因为两个用户 id 都是 1。
但实际可能是 2。
为什么?
因为你只告诉 Java:"两个 id 一样的 User 算相等。"
但你没有告诉哈希集合:"它们应该去同一个桶里找。"

这就是为什么常说:
只要重写
equals(),通常就必须重写hashCode()。
不然 HashMap、HashSet 这类集合就可能表现得很奇怪。
9. HashMap 查 key,其实就是两步
HashMap 找一个 key,可以理解成:
text
第一步:先根据 hashCode 找柜子。
第二步:柜子里再用 equals 确认是不是它。
简化伪代码:
java
// 简化理解,不是逐行源码
V get(Object key) {
int hash = key.hashCode();
Bucket bucket = findBucket(hash);
for (Node node : bucket) {
if (node.hash == hash &&
(node.key == key || key.equals(node.key))) {
return node.value;
}
}
return null;
}
注意这句:
java
node.key == key || key.equals(node.key)
意思是:
text
如果本来就是同一个对象,那肯定是同一个 key。
如果不是同一个对象,再看看 equals 认为它们是不是同一个 key。
这也是面试官喜欢从 equals() 追到 HashMap 的原因。它不是突然换题,而是顺着同一条线往下问。
10. 可变对象当 key,很容易把自己坑住
再看一个真实工作里会踩的坑。
java
User user = new User(1);
Map<User, String> map = new HashMap<>();
map.put(user, "张三");
user.setId(2);
System.out.println(map.get(user)); // 可能拿不到
为什么?
你可以想成:
text
放进去的时候,id 是 1,所以放进了 1 号柜。
后来你把 id 改成 2。
再去取的时候,系统让你去 2 号柜找。
可东西还在 1 号柜里。
所以找不到。

所以经验是:
如果一个对象要当
HashMap的 key,参与equals()和hashCode()的字段尽量不要变。
11. Objects.equals() 只是帮你少写空判断
有时你会看到这种写法:
java
Objects.equals(a, b)
它大概等于:
java
a == b || (a != null && a.equals(b))
它的好处是:不怕 a 是 null。
但它不是魔法。它不会帮你决定什么叫"业务上相等"。最终还是要看 a.equals(b) 自己怎么写。
12. Android 里这个问题特别常见
这题不是只在 Java 面试里出现。Android 里也经常遇到。
RecyclerView 列表刷新
DiffUtil 会问两个问题:
java
public class UserDiff extends DiffUtil.ItemCallback<User> {
@Override
public boolean areItemsTheSame(User oldItem, User newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(User oldItem, User newItem) {
return oldItem.equals(newItem);
}
}
这两个方法其实很像今天讲的内容。
text
areItemsTheSame:
这是不是同一条数据?
常见就是比较 id。
areContentsTheSame:
这条数据的展示内容有没有变?
可以比较整个对象内容。

这就是非常典型的"同一个"和"内容一样"分开判断。
Kotlin data class
Kotlin 里的 data class 会自动生成 equals() 和 hashCode():
kotlin
data class User(
val id: Long,
val name: String
)
这很方便。但也要注意:
text
data class 默认会把主构造里的字段都拿去比较。
如果你在列表里判断"是不是同一条数据",通常只应该看 id。
如果你判断"内容有没有变",才更适合比较整个对象。
13. 为什么面试官这么爱问这个
因为这个问题表面很小,实际可以看出很多东西。
| 面试官继续问 | 他想看什么 |
|---|---|
== 和 equals() 区别 |
你懂不懂引用和对象 |
Object.equals() 默认做什么 |
你有没有看过基础源码 |
String 为什么可以比较内容 |
你知不知道类可以重写规则 |
为什么还要 hashCode() |
你懂不懂 HashMap / HashSet |
只写 equals() 会怎样 |
你有没有踩过集合的坑 |
| 对象字段变了会怎样 | 你有没有真实工程经验 |
| DiffUtil 怎么写 | 你能不能把 Java 基础用到 Android 里 |
说白了,它不是在问你会不会背 equals(),而是在看你有没有把"对象、集合、业务身份"这三件事连起来。
14. 面试时怎么答最顺
你可以这样说:
text
判断两个对象是否相同,要先看相同的含义。
如果是判断是不是同一个对象,用 ==。
因为 == 比较的是引用地址。
如果是判断内容是否相同,用 equals。
但普通类默认 equals 其实也是 this == obj,
所以需要根据业务重写 equals。
如果对象要放进 HashMap 或 HashSet,
还必须重写 hashCode。
因为 HashMap 会先用 hashCode 找桶,
再用 == 或 equals 判断是不是同一个 key。
所以 equals 相等的对象,hashCode 必须相同。
但 hashCode 相同,不一定 equals 相同。
更口语一点:
我一般会先问清楚这个"相同"指什么。如果只是两个变量是不是指着同一个对象,那就是
==。如果是两个用户是不是业务上的同一个用户,就看equals()怎么定义,比如用用户 id 来比较。但只要这个对象要放到HashMap或HashSet里,就不能只写equals(),还要写hashCode()。因为哈希集合是先根据 hash 找桶,再在桶里用equals()确认。否则两个业务上相等的对象可能被放到不同桶里,导致去重失败或者查不到。
这个回答已经够应付大多数追问。
15. 几个常见小题
题目一
java
String a = new String("x");
String b = new String("x");
System.out.println(a == b);
System.out.println(a.equals(b));
答案:
text
false
true
因为 a 和 b 是两个对象,所以 == 是 false。它们文字内容一样,所以 equals() 是 true。
题目二
java
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
常见结果:
text
true
false
但你不要记成"127 用 ==,128 不行"。正确理解是:包装类型比较数值别依赖 ==,要用 equals() 或拆箱比较。
题目三
java
Set<User> set = new HashSet<>();
set.add(new User(1));
set.add(new User(1));
为什么可能有两个?
因为 User 可能只写了 equals(),没写 hashCode()。两个对象业务上相等,但去了不同桶,所以集合没法去重。
16. 最后总结
这道题其实一句话就能收住:
text
== 看是不是同一个对象;
equals 看业务上是不是相等;
hashCode 帮哈希集合先找到该去哪儿查。
别把它们混成一个东西。你只要能说清"我现在判断的是哪一种相同",再讲出 HashMap 为什么要同时依赖 hashCode() 和 equals(),这题就不是坑,而是一个很好的加分点。
最后我的建议是,先把概念回答上来,再去用自己的话产出相应的理解,与面试官产生共鸣点,那么就容易得分,否则,基础概念不牢靠,人家就没有被问下去的意愿了,就非常容易失分的。AI时代下,现在的面试真的跟千军万马过独木桥一样,你不够优秀,哪怕有一丁点回答地不够到位,就容易被横向对比。这也是我们为什么还需要学这么基础的,是49年入国军吗,恰恰不是,我们要在这个时代站稳脚跟,是需要去打牢这些基础根基的。