基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

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 的引用 xx.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() 方法。

hashCodeequals 的协定:

  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 语言的基石。深入理解它的这些核心方法,特别是 equalshashCode 的关系,是每一个 Java 开发者必须掌握的基础知识。

00:00
00:00