Java 中对象的几种比较方式(2025–2026 面试高频完整版)
在 Java 中,对象比较 主要有以下 4–5 种核心方式,它们用途、语义、性能、适用场景完全不同。面试中常被追问的顺序通常是:== → equals → hashCode → Comparable → Comparator。
下面按使用频率和重要性逐一拆解。
1. == (引用比较 / 身份比较)
- 比较内容:两个引用变量是否指向堆中同一个对象(内存地址是否相同)
- 适用类型:
- 基本类型 → 值比较(int、double、boolean 等)
- 引用类型 → 地址比较
- 默认行为:Object 类的原始实现就是
return this == obj; - 特点:最快,O(1),无方法调用开销
- 典型场景:
- 判断是否为同一个实例(单例模式、缓存命中)
- null 判断
if (obj == null) - 面试常问陷阱:
- String str1 = “hello”; String str2 = new String(“hello”);
→ str1 == str2 为 false(常量池 vs 堆)
2. equals() (逻辑相等 / 值比较)
- 默认行为(Object 类实现):
return (this == obj);→ 等价于 == - 重写后语义:大多数业务类重写后比较内容是否相等(而非引用)
- 经典重写类(JDK 内置已重写):
| 类 | equals 比较内容 | hashCode 是否同步重写 |
|---|---|---|
| String | 字符序列是否相同 | 是 |
| Integer | 数值是否相同 | 是 |
| BigDecimal | 数值 + 精度是否相同 | 是 |
| LocalDate | 年月日是否相同 | 是 |
| Arrays | 内容逐个 equals(deepEquals 多维) | — |
- 重写 equals 的正确姿势(契约,必须遵守):
@Override
public boolean equals(Object o) {
// 1. 引用相同 → 相等
if (this == o) return true;
// 2. 类型检查 + null 检查
if (o == null || getClass() != o.getClass()) return false;
// 3. 强制转换后逐字段比较(基本类型用 ==,对象用 equals)
Person other = (Person) o;
return age == other.age &&
Objects.equals(name, other.name) &&
Objects.equals(idCard, other.idCard);
}
- Objects.equals(a, b) 工具方法(强烈推荐):
- 内部处理了 null 安全:
a == b || (a != null && a.equals(b))
3. hashCode() (散列码比较,常配合 equals 使用)
- 作用:为对象生成一个 int 值,主要用于哈希表(HashMap、HashSet、Hashtable 等)
- 核心契约(Object 文档原文):
- 同一个对象在生命周期内多次调用 hashCode(),返回值必须相同(前提:equals 使用的字段没变)
- 如果 a.equals(b) == true,则 a.hashCode() 必须等于 b.hashCode()
- 如果 a.equals(b) == false,不要求 hashCode 不同(但最好不同,以减少哈希冲突)
- 重写 equals 必须重写 hashCode 的原因:
- 违反第 2 条契约 → HashMap/HashSet 可能出现逻辑错误(同一个对象在集合中出现多次,或 find 不到)
- 2025–2026 推荐生成方式(IDE 自动生成或以下方式):
@Override
public int hashCode() {
return Objects.hash(name, age, idCard); // 最简洁、最推荐(Java 7+)
}
- 性能提示:hashCode 分布越均匀越好,避免大量对象 hashCode 相同导致链表退化
4. Comparable 接口(自然排序)
- 包:java.lang
- 方法:
int compareTo(T o) - 返回值约定:
- 负数 → this < o
- 0 → this == o(逻辑相等,不一定引用相等)
- 正数 → this > o
- 典型实现(String、Integer、LocalDate 等都实现了):
public class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person o) {
// 先按年龄升序,年龄相同再按姓名字典序
int ageCmp = Integer.compare(this.age, o.age);
if (ageCmp != 0) return ageCmp;
return this.name.compareTo(o.name);
}
}
- 使用场景:
- Collections.sort(list)
- TreeSet、TreeMap 的默认排序
- Arrays.sort()(对象数组)
5. Comparator 接口(定制排序 / 外部比较器)
- 包:java.util
- 方法:
int compare(T o1, T o2) - 特点:不侵入原类,可创建多个比较器
- Java 8+ 推荐写法(最常用):
// 方式一:Lambda + comparing
List<Person> list = ...;
list.sort(Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName));
// 方式二:匿名内部类(老项目常见)
Comparator<Person> byAgeThenName = (p1, p2) -> {
int cmp = Integer.compare(p1.getAge(), p2.getAge());
return cmp != 0 ? cmp : p1.getName().compareTo(p2.getName());
};
- 使用场景:
- 临时排序需求
- TreeSet/TreeMap 指定比较器
- Stream.sorted(comparator)
6. 快速对比总结表(面试最爱画的表)
| 比较方式 | 比较内容 | 是否可重写 | 是否侵入类 | 典型集合支持 | 性能 | 面试出现频率 |
|---|---|---|---|---|---|---|
| == | 引用地址 | 否 | 否 | — | 极高 | ★★★★★ |
| equals() | 逻辑相等(内容) | 是 | 是 | HashMap/HashSet | 高 | ★★★★★ |
| hashCode() | 散列值 | 是(必须与equals一致) | 是 | HashMap/HashSet | 极高 | ★★★★☆ |
| compareTo() | 自然排序大小 | 是 | 是 | TreeSet/TreeMap 默认 | 中 | ★★★★ |
| Comparator | 定制排序 | — | 否 | 任意集合/Stream | 中 | ★★★★ |
7. 常见面试连环追问(建议准备的回答模板)
- 重写了 equals 没重写 hashCode 会有什么问题?
→ HashMap 中 put 后 get 找不到;Set 中出现重复元素 - String 为什么可以直接用 == 比较常量池字符串?
→ 字符串常量池 + intern() 机制 - 如何比较两个对象是否“完全相等”(包括所有字段深比较)?
→ Apache Commons Lang → ReflectionToStringBuilder 或 Objects.deepEquals(多维数组) - Java 14+ Record 类自动实现了哪些比较相关方法?
→ equals、hashCode、toString 全部按所有组件字段自动生成 - 排序时如果 compareTo 返回 0,但 equals 返回 false 会怎样?
→ TreeSet/TreeMap 会认为相等而覆盖(违反 SortedSet 语义)
希望这份总结能帮你快速建立 Java 对象比较的完整知识图谱。
如果你正在准备面试,想针对某一种方式(比如 equals + hashCode 的完整手写实现、或 Comparator 的多种写法)要更详细的代码示例,或者想看某个具体场景的对比代码,直接告诉我,我可以继续展开。