01.3 — Object 类与通用方法
定位: 一切类的根 — Object 定义了所有 Java 对象的通用协议 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: equals/hashCode 契约、clone 深浅拷贝、== vs equals
一、这是什么?为什么需要它?
是什么
Object 是所有 Java 类的隐式父类(除 primitive 外),定义了所有对象的通用行为:
java
public class Object {
// 对象比较
public boolean equals(Object obj);
public int hashCode();
// 对象复制
protected native Object clone() throws CloneNotSupportedException;
// 对象信息
public String toString();
protected void finalize() throws Throwable; // JDK9+ 废弃
// 线程协作
public final void wait() throws InterruptedException;
public final void notify();
public final void notifyAll();
// 反射
public final native Class<?> getClass();
}为什么需要这些方法?
没有这些通用方法会怎样?
- 每个类都要自己定义"相等"的语义 → 无法统一处理集合查找
- 每个集合实现都要猜怎么比较对象 → HashMap 没法工作
- 没有统一的线程协作 API → wait/notify 需要各自实现
所以:Object 定义了 Java 对象的通用契约,让集合、同步、反射等机制统一工作。
二、原理拆解
2.1 equals/hashCode 契约
契约(Java 官方规范):
1. 自反性: x.equals(x) -> true
2. 对称性: x.equals(y) == y.equals(x)
3. 传递性: x.equals(y) && y.equals(z) -> x.equals(z)
4. 一致性: 无修改时多次调用结果一致
5. equals(null) -> false
6. *** 如果 x.equals(y) == true
则 x.hashCode() == y.hashCode() (必须!)
7. 如果 x.hashCode() == y.hashCode()
x.equals(y) 不一定 true(哈希冲突)为什么这个契约重要?
HashMap 查找流程:
key.hashCode() -> 计算桶索引 -> 遍历桶内元素 -> key.equals(target)
如果只重写 equals 不重写 hashCode:
- equals 认为相同的对象,hashCode 不同
- 被放到不同桶 -> HashMap.get() 找不到!
⚡ 必须一起重写!2.2 正确的 equals 实现模板
java
@Override
public boolean equals(Object o) {
// 1. 自反性优化
if (this == o) return true;
// 2. 类型检查
if (o == null || getClass() != o.getClass()) return false;
// 3. 转型
Person person = (Person) o;
// 4. 比较关键字段
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 用和 equals 相同的字段!
}2.3 == vs equals 全景
== :
- 基本类型:比较值是否相等
- 引用类型:比较内存地址(是否是同一个对象)
equals():
- Object 默认:同 ==(比较地址)
- 重写后:比较内容(String、Integer、自定义类)java
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(不同对象)
System.out.println(s1.equals(s2)); // true(内容相等)
// 包装类陷阱
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true(缓存池,同一对象)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false(不同对象)
// 包装类比较一律用 equals()!2.4 clone() 深浅拷贝
浅拷贝(默认 clone()):
原对象 ──→ [ name ──→ "Alice" ]
↑
克隆对象 ──→ [ name ──→ 同一引用 ]
深拷贝(手动实现):
原对象 ──→ [ name ──→ "Alice" ]
↑ (不同对象)
克隆对象 ──→ [ name ──→ "Alice"(新 String)]实现要求:
- 实现
Cloneable接口(标记接口,否则抛 CloneNotSupportedException) - 重写
clone()为 public - 对于可变引用字段,递归 clone
三、图解全景
┌─────────────────────────────────────────────────────────────┐
│ Object 方法分类与应用场景 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 对象比较 │ │
│ │ equals() / hashCode() → 集合框架的核心基础 │ │
│ │ String、Integer、自定义实体类必须正确重写 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 对象复制 │ │
│ │ clone() → 需要 Cloneable 标记接口 │ │
│ │ 深拷贝 vs 浅拷贝 → 视场景选择 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 对象信息 │ │
│ │ toString() → 调试打印,建议所有实体类重写 │ │
│ │ getClass() → 反射获取运行时类型 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 线程协作 │ │
│ │ wait() / notify() / notifyAll() → synchronized 块 │ │
│ │ 必须持有锁才能调用,否则抛 IllegalMonitorStateExc │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘四、实战验证
4.1 违反 hashCode 契约的后果
java
class BadPerson {
String name;
BadPerson(String name) { this.name = name; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return name.equals(((BadPerson) o).name);
}
// 没有重写 hashCode!
}
// 测试
Set<BadPerson> set = new HashSet<>();
set.add(new BadPerson("Alice"));
System.out.println(set.contains(new BadPerson("Alice")));
// false! 不同 hashCode,不同桶,找不到!4.2 clone 深浅拷贝验证
java
class Address implements Cloneable {
String city;
Address(String city) { this.city = city; }
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
class Person implements Cloneable {
String name;
Address address;
Person(String name, String city) {
this.name = name;
this.address = new Address(city);
}
// 深拷贝
@Override
protected Person clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = this.address.clone(); // 深拷贝可变字段
return cloned;
}
}
Person p1 = new Person("Alice", "Beijing");
Person p2 = p1.clone();
System.out.println(p1.address == p2.address); // false(深拷贝成功)五、面试视角
| 追问 | 答案要点 |
|---|---|
| 为什么重写 equals 必须重写 hashCode? | 违反 hashCode 契约导致 HashMap/HashSet 行为异常 |
| equals 和 == 在 String 比较中的区别? | == 比地址;equals 比内容 |
| 为什么 Integer 127 == Integer 127 是 true? | IntegerCache 缓存 -128 ~ 127,字面量复用缓存对象 |
| clone() 为什么不默认 public? | 需要调用方显式选择深浅拷贝策略 |
| getClass() 和 instanceof 的区别? | getClass() 精确匹配;instanceof 包含子类(equals 应用 getClass) |
📚 相关链接
- **String深度解析** — String 的 equals 实现
- **包装类详解** — 包装类的 equals 陷阱
- ← 返回 **语言基础索引**