杰瑞科技汇

Java Object 源码中,构造方法为何不显式定义?

Object 类位于 java.lang 包中,由于它是一个核心类,其源码实现是 native 的,也就是说,它的方法是由底层的 C/C++ 代码实现的,而不是 Java 代码本身,我们无法直接看到其 C/C++ 的实现,但我们可以通过 Java 的文档和其行为来理解每一个方法的作用。

下面,我将详细解读 Object 类中的 11 个公共方法。


Object.java 源码结构概览

package java.lang;
public class Object {
    // 1. native 方法:本地方法,由 JVM 实现
    private static native void registerNatives();
    static {
        registerNatives();
    }
    // 2. 构造方法
    public Object() {}
    // 3. 核心方法
    public final native Class<?> getClass();
    public native int hashCode();
    public boolean equals(Object obj) { ... }
    protected native Object clone() throws CloneNotSupportedException;
    public native String toString();
    // 4. 线程相关
    public final native void notify();
    public final native void notifyAll();
    public final void wait() throws InterruptedException { ... }
    public final native void wait(long timeoutMillis) throws InterruptedException;
    public final void wait(long timeoutMillis, int nanos) throws InterruptedException { ... }
    // 5. 垃圾回收相关
    protected void finalize() throws Throwable { ... }
}

方法详解

public Object()

这是 Object 类的公共无参构造方法,由于所有类都继承自 Object,所以当你创建任何一个类的实例时,在构造函数的调用链的最底层,都会默认调用这个 Object() 构造方法。

// 下面这个类的构造过程隐式地调用了 Object()
public class MyClass {
    public MyClass() {
        // super(); // 这行代码是编译器自动添加的,调用父类 Object 的构造方法
    }
}

public final native Class<?> getClass()

  • native: 表示这个方法不是由 Java 代码实现的,而是由 JVM 的本地方法实现。
  • final: 表示这个方法不能被任何子类重写。
  • 作用: 返回此 Object 运行时的类。Class 类是一个反射 API 的入口,包含了类的完整信息,如类名、父类、接口、方法、字段等。
  • 返回值: 一个 Class 对象,代表该对象的实际类型。
  • 示例:
    String str = "Hello";
    Class<?> clazz = str.getClass(); // clazz 的类型是 java.lang.Class<String>
    System.out.println(clazz.getName()); // 输出: java.lang.String

public native int hashCode()

  • native: 由 JVM 实现。
  • 作用: 返回该对象的哈希码值,哈希码主要用于基于哈希的集合,如 HashMap, HashSet, Hashtable 等,用于确定对象在哈希表中的存储位置。
  • 重要约定:
    1. 一致性: 在程序执行期间,只要对象的 equals 方法所用的信息没有被修改,那么对同一个对象多次调用 hashCode 方法,必须返回相同的整数,但在同一应用程序的多次执行中,这个整数可以不同。
    2. 相等的对象必须有相同的哈希码: a.equals(b)truea.hashCode() 必须等于 b.hashCode()
    3. 不相等的对象,哈希码不一定不同: a.equals(b)falsea.hashCode()b.hashCode() 可以相同,这被称为“哈希碰撞”,哈希表通过链地址法或开放地址法来处理碰撞。
  • 默认实现: JVM 默认的实现是基于对象的内存地址来生成一个哈希码,两个不同的对象(即使内容相同),其哈希码也大概率是不同的。
  • 重写: 当你重写 equals 方法时,必须同时重写 hashCode 方法,以遵守上述约定,否则,对象将无法正确地在哈希集合中工作。

public boolean equals(Object obj)

  • 作用: 判断两个对象是否“相等”,这个“相等”指的是逻辑上的相等,而不是内存地址的相同。
  • 默认实现: 默认的 equals 方法通过比较两个对象的内存地址来判断是否相等,即 this == obj,这意味着只有两个引用指向同一个堆内存中的对象时,equals 才返回 true
  • 重写规则: 当你需要自定义对象的“相等”逻辑时(比较 Person 对象的 id 是否相同),应该重写此方法,重写时需遵循以下约定:
    1. 自反性: x.equals(x) 必须返回 true
    2. 对称性: x.equals(y) 返回 truey.equals(x) 也必须返回 true
    3. 传递性: x.equals(y) 返回 true,且 y.equals(z) 返回 truex.equals(z) 也必须返回 true
    4. 一致性: 多次调用 x.equals(y),只要对象内容未变,结果必须一致。
    5. 非空性: x.equals(null) 必须返回 false
  • 最佳实践重写模板:
    @Override
    public boolean equals(Object o) {
        // 1. 检查是否是同一个对象
        if (this == o) return true;
        // 2. 检查是否为 null 或类型是否匹配
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 类型转换
        MyClass myClass = (MyClass) o;
        // 4. 比较关键字段(通常先比较基本类型或非null对象)
        return myClass.id == this.id && Objects.equals(this.name, myClass.name);
    }

protected native Object clone() throws CloneNotSupportedException

  • native: 由 JVM 实现。
  • 作用: 创建并返回此对象的一个副本(拷贝)。
  • 浅拷贝 vs 深拷贝:
    • 浅拷贝: clone() 方法默认执行的是浅拷贝,它会创建一个新的对象,并将原对象中所有基本类型字段的值复制到新对象中,对于引用类型字段,它只复制引用(地址),而不是引用所指向的对象,新对象和原对象的引用类型字段指向的是同一个内存地址。
    • 深拷贝: 如果需要实现深拷贝,你必须重写 clone() 方法,并在其中递归地拷贝所有引用指向的对象。
  • Cloneable 接口: Object 类的 clone() 方法是受保护的,如果一个对象想要被克隆,它的类必须实现 java.lang.Cloneable 接口,如果一个类没有实现 Cloneable 接口,调用其 clone() 方法会抛出 CloneNotSupportedException 异常。
  • 使用场景: 克隆模式在实际开发中争议较大,因为它可能会破坏封装性,并且代码容易出错,通常推荐使用拷贝构造器或工厂模式来替代。

public String toString()

  • 作用: 返回一个表示该对象的字符串,这个字符串通常应该是一个“简洁但信息丰富”的表示,便于调试和日志记录。
  • 默认实现: 返回一个格式为 “类名@十六进制哈希码” 的字符串。com.example.MyClass@1a2b3c4d
  • 重写: 几乎所有有意义的类都应该重写 toString() 方法,以便提供更有意义的对象信息。
  • 示例:
    @Override
    public String toString() {
        return "MyClass{" +
               "id=" + id +
               ", name='" + name + '\'' +
               '}';
    }

线程通信方法 (notify, notifyAll, wait)

这些方法用于多线程环境下的线程间通信,它们都作用于对象级别的锁(monitor)上。

  • public final native void notify():

    唤醒在此对象监视器上等待的单个线程,选择哪个线程是随意的,由 JVM 决定。

  • public final native void notifyAll():
    • 唤醒在此对象监视器上等待的所有线程。
  • public final native void wait(long timeoutMillis)public final void wait(long timeoutMillis, int nanos):
    • 让当前线程等待,直到其他线程调用此对象的 notify()notifyAll() 方法,或者超过了指定的等待时间。
  • public final void wait():
    • 让当前线程无限期等待,直到其他线程调用 notify()notifyAll()
  • 重要: 这些方法只能在同步代码块或同步方法中调用,否则会抛出 IllegalMonitorStateException 异常,它们是 Java 对象锁机制的核心。

protected void finalize() throws Throwable

  • 作用: 这个方法被称为终结方法,当垃圾回收器确定没有对该对象的更多引用时,由垃圾回收器调用此方法。
  • 注意: finalize() 方法非常不可靠,并且在 Java 9 之后被标记为 deprecated(不推荐使用),原因如下:
    1. 执行时机不确定: JVM 不保证 finalize() 何时被调用,甚至在程序退出时可能都不会被调用。
    2. 性能开销: finalize 会影响垃圾回收的性能。
    3. 不保证执行: finalize 方法中抛出了未捕获的异常,垃圾回收过程会终止,且该对象可能永远不会被回收。
  • 替代方案: 如果需要资源清理(如关闭文件、数据库连接等),应该使用 try-finally 块或 try-with-resources 语句来确保资源被正确释放。finalize 仅作为在 Java 早期版本中处理资源的一种补充,现已不推荐。

方法 修饰符 作用 是否可重写 备注
getClass() public final native 获取对象的运行时类 返回 Class 对象,是反射的入口
hashCode() public native 获取对象的哈希码 重写 equals 时必须重写此方法
equals() public 判断两个对象是否逻辑相等 默认比较内存地址,需遵循约定重写
clone() protected native 创建对象副本 需实现 Cloneable 接口,默认为浅拷贝
toString() public 返回对象的字符串表示 默认返回 类名@哈希码,建议重写
notify() public final native 唤醒一个等待线程 多线程通信,需在同步代码块中调用
notifyAll() public final native 唤醒所有等待线程 多线程通信,需在同步代码块中调用
wait() public final 使当前线程等待 多线程通信,需在同步代码块中调用
finalize() protected 对象被回收时调用 已废弃,不推荐使用

掌握 Object 类的这些方法,特别是 equals, hashCode, 和 toString 的正确使用,是成为一名合格 Java 程序员的关键一步,它们是 Java 面向对象和集合框架的基石。

分享:
扫描分享到社交APP
上一篇
下一篇