在 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 堆)
- string str1 = “hello”; string str2 = new string(“hello”);
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))
- 内部处理了 null 安全:
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 的多种写法)要更详细的代码示例,或者想看某个具体场景的对比代码,直接告诉我,我可以继续展开。
以上就是java中对象比较的五种方式详解的详细内容,更多关于java对象比较方式的资料请关注代码网其它相关文章!
发表评论