解析 Java 根基:Object 类核心方法深度详解
java.lang.Object 是 Java 中所有类的直接或间接父类,是整个类型体系的根。
理解 Object 的每个方法及其实现细节、默认行为、重写场景、注意事项,是 Java 开发者的基本功,也是面试中高频且容易挖深度的题目。
Object 的 11 个核心方法一览表
| 方法签名 | 作用简述 | 是否默认有意义 | 是否建议重写 | 常见重写场景 | 是否线程安全(默认) |
|---|---|---|---|---|---|
public final Class<?> getClass() | 获取运行时类型信息 | 有意义 | 禁止重写 | — | 是 |
public int hashCode() | 获取对象的哈希码 | 有意义 | 强烈建议 | 放入 HashMap/HashSet 时 | 否(需自己保证) |
public boolean equals(Object obj) | 判断对象是否“相等” | 有意义 | 强烈建议 | 与 hashCode 一起重写 | 否 |
protected Object clone() | 浅拷贝对象 | 有意义 | 视情况 | 需要拷贝对象时 | 否 |
public String toString() | 对象的字符串表示 | 有意义(但很丑) | 强烈建议 | 日志、调试、打印对象 | 是 |
public final void notify() | 唤醒在此对象监视器上等待的一个线程 | 有意义 | 禁止重写 | 线程通信(wait/notify 机制) | — |
public final void notifyAll() | 唤醒在此对象监视器上等待的所有线程 | 有意义 | 禁止重写 | 线程通信 | — |
public final void wait() | 当前线程等待,直到被 notify/notifyAll | 有意义 | 禁止重写 | 线程通信 | — |
public final void wait(long timeout) | 带超时等待 | 有意义 | 禁止重写 | — | — |
public final void wait(long timeout, int nanos) | 更精确的超时等待 | 有意义 | 禁止重写 | — | — |
protected void finalize() | 对象被 GC 前可能被调用(已废弃) | 无意义 | 不建议 | — | — |
一、最常被重写的三个方法(面试重点)
1. equals() 与 hashCode() —— 必须一起重写
默认行为:
equals()默认比较的是对象地址(==)hashCode()默认返回的是对象在内存中的某种哈希值(通常与地址相关)
契约(Contract)——必须遵守的规则:
- 相等性一致:如果
a.equals(b)返回 true,则a.hashCode()必须等于b.hashCode() - hashCode 一致性:同一个对象在程序运行期间多次调用 hashCode(),只要 equals 使用的信息没变,返回值必须相同
- 非空性:
x.equals(null)永远返回 false - 对称性:
a.equals(b)⇔b.equals(a) - 传递性:
a.equals(b) && b.equals(c)⇒a.equals(c)
正确重写姿势(推荐模板):
@Override
public boolean equals(Object o) {
if (this == o) return true; // 同一对象
if (o == null || getClass() != o.getClass()) // 类型不同
return false;
Person person = (Person) o; // 安全转型
return age == person.age && // 基本类型直接 ==
Objects.equals(name, person.name) && // 使用工具类避免空指针
Objects.equals(idCard, person.idCard);
}
@Override
public int hashCode() {
return Objects.hash(name, age, idCard); // 推荐使用 Objects.hash
}
常见错误:
- 只重写了 equals 没重写 hashCode → HashMap/HashSet 失效
- 用
==比较对象字段 → 永远不等 - 没有处理 null → NullPointerException
- 用了可变字段参与 equals/hashCode → 放入集合后修改导致找不到
2. toString() —— 日志、调试、打印的门面
默认实现:类名@十六进制hashCode(非常难读)
推荐重写方式:
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", idCard='" + idCard + '\'' +
'}';
}
现代最佳实践(强烈推荐):
- 使用 Lombok 的
@ToString - 或使用 commons-lang3 的
ToStringBuilder - 或使用 JSON 序列化(调试时很友好)
3. clone() —— 浅拷贝与深拷贝
默认行为:
- protected 方法
- 实现 Cloneable 接口后才能调用,否则抛 CloneNotSupportedException
- 是浅拷贝(只复制引用,对象内部引用字段仍指向同一对象)
两种常见实现方式:
// 方式1:实现 Cloneable(最常见)
public class Person implements Cloneable {
private String name;
private int age;
private Address address; // 引用类型
@Override
protected Person clone() throws CloneNotSupportedException {
Person p = (Person) super.clone(); // 浅拷贝
p.address = address.clone(); // 手动深拷贝引用字段
return p;
}
}
// 方式2:推荐 —— 使用拷贝构造器或拷贝工厂(更安全、更清晰)
public Person(Person other) {
this.name = other.name;
this.age = other.age;
this.address = new Address(other.address); // 深拷贝
}
结论:现代 Java 项目中,clone() 方法使用率很低,更推荐使用:
- 拷贝构造器
- 拷贝工厂方法
- 第三方库(如 Apache Commons BeanUtils、Dozer、MapStruct)
- 序列化方式(JSON、Protobuf)
二、被 final 修饰禁止重写的 5 个方法
- getClass()
- wait() / wait(long) / wait(long,int)
- notify() / notifyAll()
这些方法涉及 JVM 底层实现和线程调度逻辑,禁止重写非常合理。
三、finalize() —— 已被废弃的“遗物”
- Java 9 开始标记为 @Deprecated
- 原因:不可预测、性能差、容易造成内存泄漏、替代方案更好(Cleaner、try-with-resources)
- 永远不要依赖 finalize() 做资源释放
四、面试高频追问总结
- 为什么 equals 和 hashCode 要一起重写?
- 如果只重写 equals 不重写 hashCode 会发生什么?
- hashCode 相等,equals 一定相等吗?反过来呢?
- clone 是深拷贝还是浅拷贝?怎么实现深拷贝?
- Object 的 wait/notify 机制原理是什么?为什么必须在 synchronized 里用?
- toString() 不重写会有什么影响?
- getClass() 为什么是 final 的?
希望这份总结能让你对 Object 类的理解从“知道有这些方法”升级到“知道每个方法的设计意图、默认行为、重写规范、常见陷阱”。
如果你想继续深入某个方法(比如手写一个安全的深拷贝、wait/notify 经典实现、生产级 equals/hashCode 写法),或者想看某个具体面试题的完整回答范例,随时告诉我!