Java 中对象的几种比较方式(2025-2026 面试高频总结)
在 Java 中,比较两个对象是否“相等”或“大小关系”,主要有以下几种常见方式,每种方式适用的场景和语义完全不同:
| 比较方式 | 方法/运算符 | 比较内容 | 是否可重写/自定义 | 适用场景 | 是否考虑 null | 典型实现类示例 |
|---|---|---|---|---|---|---|
| 引用比较(==) | == | 内存地址是否相同 | 不可 | 判断是否为同一个对象实例 | 是 | 所有对象 |
| 值相等比较(equals) | equals() | 业务上是否“内容相等” | 可重写 | 集合判重、Map 键比较、业务相等判断 | 需手动处理 | String、Integer、自定义实体类 |
| 自然顺序比较 | compareTo() | 对象之间的自然排序大小 | 可实现 Comparable | TreeSet、TreeMap、Collections.sort() | 需手动处理 | String、Integer、BigDecimal |
| 自定义比较器 | Comparator | 灵活的外部排序规则 | 可新建实例 | 同一类型对象不同场景不同排序规则 | 需手动处理 | Collections.sort(list, comp) |
| 哈希一致性比较 | hashCode() + equals() | 哈希表使用时的快速筛选+精确比较 | 必须同时重写 | HashMap、HashSet、ConcurrentHashMap | 需手动处理 | 自定义对象作为 HashMap key 时 |
| 深度比较(对象图) | Objects.deepEquals() | 递归比较对象及其引用对象内容 | — | 单元测试、复杂对象全等判断 | 友好处理 | 测试框架、日志对比 |
| 记录类(Record)相等 | 自动生成 equals | 所有组件字段是否全部相等 | 不可重写 | 值对象、DTO、不可变数据载体 | 友好处理 | Java 14+ 的 record 类型 |
详细说明与代码示例
1. 最基础:引用相等(==)
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false ← 不同的对象实例
System.out.println(s1 == "hello"); // false ← 常量池与堆对象不同
2. 内容相等(equals)——最常被问的重写规则
class User {
private Long id;
private String name;
@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); // 通常业务上只比 id
}
@Override
public int hashCode() {
return Objects.hash(id); // 必须与 equals 保持一致
}
}
重要原则(面试必背):
- 自反性:x.equals(x) → true
- 对称性:x.equals(y) ⇔ y.equals(x)
- 传递性:x.equals(y) && y.equals(z) → x.equals(z)
- 一致性:多次调用结果相同(前提对象状态不变)
- 与 null:x.equals(null) 永远返回 false
3. 自然排序(Comparable)
class Student implements Comparable<Student> {
String name;
int age;
@Override
public int compareTo(Student o) {
// 先按年龄升序,年龄相同按姓名降序
int ageCompare = Integer.compare(this.age, o.age);
if (ageCompare != 0) return ageCompare;
return o.name.compareTo(this.name); // 降序
}
}
4. 自定义比较器(Comparator)——最灵活的方式
// 按姓名长度排序
Comparator<Student> byNameLength = (s1, s2) ->
Integer.compare(s1.name.length(), s2.name.length());
// 链式比较(Java 8+)
Comparator<Student> complex = Comparator
.comparing(Student::getAge)
.thenComparing(Student::getName, Comparator.reverseOrder());
5. 现代推荐写法(Java 8+ 常用工具类)
// 安全比较(防 NPE)
Objects.equals(obj1, obj2); // null 安全
Objects.deepEquals(obj1, obj2); // 深度比较数组、集合等
// 比较大小(防 NPE)
Integer.compare(a, b); // 代替 a - b(防止溢出)
Long.compare(a, b);
6. 快速决策表(面试/实战常用)
| 你要做什么? | 推荐方式 | 备注 |
|---|---|---|
| 判断两个变量是否指向同一个对象 | == | 最快 |
| 判断两个对象内容是否业务相等 | equals() | 最常用 |
| 把对象放进 HashMap / HashSet 做 key | 同时重写 equals + hashCode | 必须遵守契约 |
| 对象需要排序(TreeSet / TreeMap) | 实现 Comparable 或传 Comparator | — |
| 同一个类型对象在不同场景需要不同排序规则 | 使用 Comparator | 推荐 |
| 需要比较两个复杂对象是否完全一样(测试) | Objects.deepEquals() | 方便 |
| Java 14+ 值对象、DTO | 使用 record 类型 | 自动生成完美 equals/hashCode |
一句话总结(可直接用于面试结尾):
Java 中对象的比较从最快的 引用比较(==) 到最严格的 业务相等(equals),再到排序用的 Comparable/Comparator,每一种方式都有明确的使用边界和契约。
实际开发中最容易出错的就是 equals 与 hashCode 不配对、或者在可变对象上不当使用它们作为集合 key。
需要我针对某个具体场景再深入讲解吗?
比如:
- 实体类作为 Map Key 的正确写法
- record 与普通类的 equals 差异
- 多字段排序的 10 种写法对比
- equals 与 == 在各种包装类中的真实表现 等