Skip to content

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)]

实现要求

  1. 实现 Cloneable 接口(标记接口,否则抛 CloneNotSupportedException)
  2. 重写 clone() 为 public
  3. 对于可变引用字段,递归 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 陷阱
  • ← 返回 **语言基础索引**

Knowledge4J — Java 知识库