基于本文回答

Java Object类核心方法精解

知识点图片

本文深入解析了Java所有类的根——Object类。重点讲解toString()、equals()和hashCode()等核心方法的作用、默认行为、重写规则与最佳实践,并简要介绍了线程通信、克隆等其他方法。

我们来详细、系统地梳理一下 Java 的 java.lang.Object 类。

1. 什么是 Object 类?

java.lang.Object 类是 Java 类层次结构的根(Root)。它是所有类的“始祖”。

  • 所有类的父类:在 Java 中,如果你创建一个类而没有明确地指定它的父类(使用 extends 关键字),那么它会默认继承 Object 类。
  • 无处不在:由于所有类都直接或间接地继承自 Object 类,因此任何类的对象都可以调用 Object 类中定义的方法。
  • 自动导入:Object 类位于 java.lang 包中,这个包会被 Java 编译器自动导入,所以你无需手动 import java.lang.Object;。

2. Object 类的核心方法详解

理解 Object 类的关键在于掌握它提供的几个核心方法。这些方法为所有 Java 对象提供了基础行为。


public String toString()

  • 作用:返回该对象的字符串表示形式。
  • 默认实现:默认情况下,toString() 返回的字符串格式是 类名@十六进制的哈希码。例如:com.example.Person@15db9742。
  • 为何重写(Override):这个默认输出通常没什么意义。为了方便调试、打印日志或直接输出对象信息,我们应该重写 toString() 方法,让它返回包含对象有意义状态的字符串。

示例:

java
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写前,打印对象会得到类似 Person@15db9742
    
    @Override
    public String toString() {
        return "Person[name=" + name + ", age=" + age + "]";
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);
        // 重写后,打印对象会得到有意义的信息
        System.out.println(person); // 输出: Person[name=Alice, age=30]
    }
}

public boolean equals(Object obj)

  • 作用:判断当前对象与传入的 obj 对象是否“相等”。
  • 默认实现:Object 类的默认实现是比较两个对象的内存地址,等同于 == 操作符。
  • 为何重写:== 比较的是“身份”(是不是同一个对象),而我们通常更关心“值”(内容是否相同)。例如,我们希望两个 Person 对象只要姓名和年龄相同,就被认为是相等的。

重写 equals 的五个约定(The equals Contract):

  1. 自反性 (Reflexive):x.equals(x) 必须返回 true。
  2. 对称性 (Symmetric):如果 x.equals(y) 返回 true,那么 y.equals(x) 也必须返回 true。
  3. 传递性 (Transitive):如果 x.equals(y) 为 true,且 y.equals(z) 为 true,那么 x.equals(z) 也必须为 true。
  4. 一致性 (Consistent):只要对象未被修改,多次调用 x.equals(y) 的结果应该保持一致。
  5. 非空性 (Non-nullity):对于任何非 null 的引用 x,x.equals(null) 必须返回 false。

示例:

java
class Person {
    // ... 其他代码 ...

    @Override
    public boolean equals(Object obj) {
        // 1. 检查是否是同一个对象
        if (this == obj) return true;
        // 2. 检查 obj 是否为 null,以及类型是否匹配
        if (obj == null || getClass() != obj.getClass()) return false;
        // 3. 类型转换并比较核心字段
        Person person = (Person) obj;
        return age == person.age && java.util.Objects.equals(name, person.name);
    }
}

public int hashCode()

  • 作用:返回该对象的哈希码(一个整数)。这个方法主要用于提高基于哈希的集合(如 HashMap, HashSet, Hashtable)的性能。
  • 默认实现:通常是根据对象的内存地址转换得来的一个整数。
  • 为何重写:为了遵守一个非常重要的规则:如果重写了 equals() 方法,就必须重写 hashCode() 方法。

hashCode 和 equals 的协定:

  1. 如果两个对象通过 equals() 方法比较是相等的,那么它们的 hashCode() 方法必须返回相同的值。
  2. 如果两个对象通过 equals() 方法比较是不相等的,它们的 hashCode() 值可以相同,也可以不同(但为了性能,最好不同)。

为什么?
HashMap 等集合在存取对象时,会先用 hashCode() 快速定位到一个“桶”(bucket),然后再用 equals() 在这个小范围里精确查找。如果 equals 相同的对象 hashCode 不同,那么它们会被放到不同的桶里,导致 map.get(obj) 可能永远找不到那个你认为存在的对象。

示例:

java
import java.util.Objects;

class Person {
    // ... 其他代码 ...

    @Override
    public int hashCode() {
        // 使用 Objects.hash() 是一个现代且安全的方式来生成哈希码
        return Objects.hash(name, age);
    }
}

public final Class<?> getClass()

  • 作用:返回对象的运行时类 (Class 对象)。Class 对象包含了类的元数据(如类名、方法、字段等)。
  • final 方法:此方法是 final 的,意味着不能被子类重写。这保证了你总能获取到一个对象最真实的类型。
  • 应用场景:主要用于反射(Reflection)。

示例:

java
Person p = new Person("Bob", 25);
Class<?> personClass = p.getClass();
System.out.println(personClass.getName());      // 输出: com.example.Person
System.out.println(personClass.getSimpleName()); // 输出: Person

protected Object clone() throws CloneNotSupportedException

  • 作用:创建并返回此对象的一个副本(克隆)。
  • 实现复杂性:
    1. 一个类必须实现 Cloneable 标记接口,否则调用 clone() 会抛出 CloneNotSupportedException。
    2. clone() 方法默认实现的是浅拷贝(Shallow Copy):它会创建一个新对象,然后将原对象的所有字段的值复制到新对象中。对于基本类型字段,这没问题;但对于引用类型字段,它只复制引用地址,导致原对象和克隆对象共享同一个内部对象。
    3. 为了实现深拷贝(Deep Copy),你需要重写 clone() 方法,并手动克隆所有可变的引用类型字段。
  • 现代实践:由于 clone() 机制复杂且易错,现在更推荐使用拷贝构造函数或静态工厂方法来实现对象复制。

protected void finalize() throws Throwable

  • 作用(历史):在垃圾收集器准备回收一个对象所占用的内存之前,会调用该对象的 finalize() 方法。
  • 已废弃(Deprecated):从 Java 9 开始,这个方法被正式标记为废弃。
  • 为什么废弃:
    1. 不确定性:无法保证 finalize() 会在何时执行,甚至无法保证它一定会执行。
    2. 性能问题:它会拖慢垃圾回收的速度。
    3. 容易出错:如果在 finalize() 中发生异常,它会被忽略,且不会终止程序。
  • 现代替代方案:使用 try-with-resources 语句和 AutoCloseable 接口来管理资源(如文件、数据库连接等)。永远不要依赖 finalize() 来释放资源。

多线程相关方法: wait(), notify(), notifyAll()

这三个方法是 Java 实现线程间通信和协作的基础,它们必须在 synchronized 代码块或方法中调用,因为它们操作的是对象的监视器锁(monitor lock)。

  • public final void wait(): 使当前线程进入等待状态,并释放它持有的对象锁。线程会一直等待,直到其他线程调用该对象的 notify() 或 notifyAll() 方法。
  • public final void notify(): 随机唤醒一个正在等待该对象锁的线程。
  • public final void notifyAll(): 唤醒所有正在等待该对象锁的线程。

这些方法是 Java 并发编程的底层构建块,通常在实现生产者-消费者模式等场景中使用。

总结

方法 主要作用 是否建议重写 关键点
toString() 返回对象的字符串表示 强烈建议 为了可读性和调试方便。
equals(Object) 比较对象是否"逻辑相等" 按需重写 重写时必须遵守五个约定。
hashCode() 返回对象的哈希码 与 equals 联动 如果重写了 equals,就必须重写 hashCode。
getClass() 获取对象的运行时类型 不可重写 (final) 用于反射。
clone() 创建对象副本 不推荐 机制复杂,易出错。推荐使用拷贝构造函数替代。
finalize() (已废弃) 垃圾回收前清理 绝对不要用 已被废弃,使用 try-with-resources 替代。
wait/notify/notifyAll 线程间通信 不可重写 (final) 用于底层并发控制,必须在 synchronized 块中使用。

Object 类是 Java 语言的基石。深入理解它的这些核心方法,特别是 equals 和 hashCode 的关系,是每一个 Java 开发者必须掌握的基础知识。

右滑查看面试常问