为什么总爱问Java中怎么判断两个对象是相同的

笔者去面试的时候,面试官冷不丁地问"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();如果这个对象要放进 HashMapHashSet,还得保证 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 == bfalse

因为 new 了两次,就像打印了两张内容一样的表。表上的内容一样,但它们不是同一张纸。

为什么 a == ctrue

因为 c = a,就像 ac 都指着同一张纸。

一句话:

== 不关心对象里面写了什么,只关心左右两边是不是指向同一个对象。

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 == bfalse,因为这是两个对象。

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" 这种字面量会放到字符串常量池里。s1s2 指向同一个池子里的字符串,所以 s1 == s2true

new String("hello") 是重新创建了一个对象,所以 s1 == s3false

但它们文字内容一样,所以 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 会缓存 -128127Integer 对象。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()

不然 HashMapHashSet 这类集合就可能表现得很奇怪。

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 来比较。但只要这个对象要放到 HashMapHashSet 里,就不能只写 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

因为 ab 是两个对象,所以 == 是 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年入国军吗,恰恰不是,我们要在这个时代站稳脚跟,是需要去打牢这些基础根基的。

相关推荐
怕浪猫5 小时前
构建你自己的 AI Agent 发行版:从 Profile 定制到生产部署全流程
前端·后端·面试
未秃头的程序猿8 小时前
全链路追踪落地一年:从翻日志到秒级定位,我们都做了什么
java·后端·面试
SamDeepThinking8 小时前
new Thread()之后发生了什么?
java·后端·面试
晚安日记wanna8 小时前
Vue3 script setup 的四层追问答到第三层才算过关
前端·vue.js·面试
晚安日记wanna8 小时前
一条 SMEMBERS 干瘫 Redis 节点:单线程的真正边界在哪
redis·后端·面试
Moment9 小时前
为什么越来越多开发者开始用 PostgreSQL?
前端·后端·面试
XuCoder9 小时前
模型吐出来的总是一段话,怎么让它乖乖按格式返回?
面试
DeepAgent9 小时前
AI Agent 面试篇(03):在线测评——认知、性格、情境题到底怎么考
面试
jianqiang.xue10 小时前
如何做好IT类的技术面试?
面试·职场和发展