JDK8 源码阅读:java.lang.Object 类

记录下自己阅读过程的笔记,如有错误,欢迎指正!

源码参考:https://github.com/kangjianwei/LearningJDK/tree/master

1. 基本介绍

  • 在 Java 中,Object 类是类层次结构的根类
  • 几乎每个 Java 类都直接或间接继承自 Object 类,意味着每个类都继承了 Object 的方法
  • 类结构

2. 源码分析

2.1 静态代码块及本地方法注册

// 本地方法的声明(使用 native 关键字)
private static native void registerNatives();
static { // 一个静态代码块
registerNatives();
}
  • 本地方法声明registerNatives()

    • 关键字 native 表示这个方法是通过本地代码(如 C/C++)实现的接口,这些代码直接与底层系统的资源或系统 API 交互

    registerNatives() 方法的主要作用是在内存中注册由本地方法实现的一些关键函数,这些函数通常是性能敏感的,直接与操作系统层交互。这样做可以提高方法调用的效率,避免每次调用时都进行查找和链接过程

  • 静态初始化块

    • 在类被 Java 虚拟机加载时执行,且只执行一次,静态代码块常用于执行类级别的初始化代码
    • 代码块中调用了 registerNatives() 方法来初始化与对象操作相关的本地方法,确保这些方法在被 Java 代码调用前已正确链接

2.2 getClass()

// 返回当前对象所属的类的类对象
public final native Class<?> getClass();
  • 这里的 Class<?> 表示方法返回一个 Class 类型的对象,其中 ? 是一个通配符,表示任何类型。在实际使用中,这个返回类型会更具体地表达为 Class<? extends |X|>,这里的 |X| 表示调用 getClass() 的对象的类型
  • 功能描述getClass() 方法返回一个 Class 类型的对象,该对象在 Java 中代表了调用此方法的对象的实际运行时类,通过这个返回的 Class 对象,可以访问关于类的各种信息,如类的名字、包含的方法、实现的接口

2.3 hashCode()

// 返回当前对象的哈希码
public native int hashCode();
  • 功能描述
  • hashCode() 方法的主要功能是为对象提供一个哈希码值,这个值主要被用于支持哈希表的使用,例如在 Java 集合框架中广泛使用的 HashMap
  • 哈希码是根据对象的内部状态计算出的一个整数,用于确定对象在哈希表中的位置,以实现快速的查找、插入和删除操作
  • 约定和规则
    • 重复调用一致性:对同一个对象多次调用 hashCode() 方法,必须始终返回相同的整数,前提是对象用于 equals 比较的信息没有被修改
    • 等价对象的哈希码相等:为了确保对象能在哈希表中正确存储和检索,规定如果两个对象通过 equals(Object) 方法判断相等,那么这两个对象调用 hashCode() 必须返回相同的整数值
    • 不等对象的哈希码优化:为了提高哈希表的性能,最好为不同的对象生成不同的哈希码,减少哈希碰撞,从而优化数据结构的性能

2.4 equals(Object obj)

// 判断两个对象是否等价
// 默认的实现只简单地比较两个对象的引用是否相同
public boolean equals(Object obj) {
return (this == obj);
}
  • 功能描述
    • equals() 方法用于确定调用对象与作为参数传递的对象(obj)是否 “相等”
    • Object 类的实现中,仅当两个对象引用指向同一内存地址时,才认为它们相等
  • 等价关系规则
    • 自反性:对于任何非空引用值 xx.equals(x) 必须返回 true
    • 对称性:对于任何非空引用值 xyx.equals(y) 应当仅在 y.equals(x) 也返回 true 时返回 true
    • 传递性:如果 x.equals(y)y.equals(z) 都返回 true,则 x.equals(z) 也应返回 true
    • 一致性:只要对象 xy 的等价比较中用到的信息没有改变,反复调用 x.equals(y) 应当始终返回同样的结果
    • 对 null 的比较:对于任何非空引用值 xx.equals(null) 应当返回 false
  • 实现注意事项
    • 在自定义类中重写 equals() 方法时,应确保遵守上述等价关系的规则
    • 当重写 equals() 方法时,也应该重写 hashCode() 方法,以保持 hashCode() 的一般约定,即相等的对象必须具有相等的哈希码
  • 方法作用
    • Java 中,不同于基本数据类型的比较(使用 ==),对象比较更复杂,因为对象的 “等价性” 可以根据实际需求定义(如属性值相等),equals() 方法提供了一种标准方式来定义两个对象是否等价
    • 在 Java 的集合框架中,例如 HashMapHashSet,对象是否相等直接影响到对象的存储和检索,正确实现 equals() 方法是使用这些集合的前提

2.5 clone()

// 浅拷贝,使用时往往需要重写为 public 形式
// 注意: 要求被克隆的对象所属的类实现 Cloneable 接口
protected native Object clone() throws CloneNotSupportedException;
  • 功能描述

    • clone() 方法的目的是创建一个新的对象,这个新对象在逻辑上与原始对象相等,但在内存中占用不同的位置(即 x.clone() != x 总是为真)
    • 它实现了对象的浅拷贝,即拷贝对象及其非静态字段,但不递归复制字段指向的对象
  • 浅拷贝 VS 深拷贝

    • 浅拷贝:默认的 clone() 方法实现是浅拷贝,意味着对象的非对象字段(如基本数据类型字段)会被完全复制,但对象字段仅复制引用,不复制引用的对象本身
    • 深拷贝:需要手动实现,在 clone() 方法中逐个复制对象内部所有可变的引用类型字段。通常涉及到修改 super.clone() 返回的对象的一个或多个字段,确保所有内部的复杂结构都得到适当的复制
  • 使用限制:Java 要求任何使用 clone() 方法的类必须实现 Cloneable 接口。这个接口是一个标记接口,不包含任何方法,其目的是表明类的设计者已经考虑到了复制问题,并且该类支持进行字段内容的复制

    • 如果一个类没有实现 Cloneable 接口而调用 clone() 方法,将抛出 CloneNotSupportedException异常
  • 特殊情况-数组

    • 所有的数组类型都被视为实现了 Cloneable 接口,因此数组总是可以被克隆,数组类型的 clone() 方法返回的类型也是相同的数组类型 (T[])

2.6 toString()

// 生成对象的字符串表示,往往需要重写
// 默认的实现是将类名与对象的哈希码以十六进制形式结合起来
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • toString() 方法提供了一种将对象转换为人类可读的字符串形式的方式,对于调试和日志记录非常有用,建议所有子类重写此方法

2.7 notify()

// 随机唤醒某个具有相同锁的对象从 wait 状态进入争锁状态
// 方法必须在同步方法或同步块中被调用,以确保调用它的线程持有当前对象的锁
public final native void notify();
  • notify() 方法的核心功能是在多线程环境中管理线程的等待与唤醒机制。它被用来唤醒一个在此对象上调用 wait() 方法而进入等待状态的线程

  • 随机唤醒:当多个线程在某个对象的锁上调用 wait() 方法后进入等待状态时,notify() 方法可以从这些等待的线程中随机选择一个线程,使其从等待状态中返回,准备继续执行

  • 线程状态变化

    • 被唤醒的线程仍需竞争对象锁。只有当前持有锁的线程释放锁后,被唤醒的线程才有机会获取锁并继续执行
    • 唤醒并不意味着立即执行。被唤醒的线程将重新参与竞争该对象锁,其竞争的条件与其他可能正在等待锁的线程无异
  • 调用条件notify() 方法必须由锁的当前持有者调用,即在对象的同步方法或同步块中使用。如果一个线程试图在未持有对象锁的情况下调用 notify(),会抛出 IllegalMonitorStateException

  • 线程如何成为锁的当前持有者

    • 对象的同步实例方法:锁定调用该方法的对象实例
    public synchronized void synchronizedMethod() {
    // ...
    }
    • 同步语句块:可以指定锁定任何对象,包括 this(当前实例)、类对象或任何其他对象
    public void someMethod() {
    synchronized (this) {
    // ...
    }
    }
    • 类的同步静态方法:锁定的是类的 Class 对象,而非类的某个实例
    public static synchronized void synchronizedStaticMethod() {
    // ...
    }

2.8 notifyAll()

// 唤醒所有具有相同锁的对象从 wait 状态进入争锁状态
// 方法必须在同步方法或同步块中被调用,以确保调用它的线程持有当前对象的锁
public final native void notifyAll();
  • 全面唤醒:相比 notify() 随机唤醒单个线程,notifyAll() 确保所有等待的线程都能得到处理机会
  • 竞争公平:所有被唤醒的线程将与其他可能正在尝试同步此对象的线程一起,公平地竞争获取对象锁
  • 使用条件与规范notifyAll() 方法必须由锁的当前持有者调用。如果在未持有对象锁的情况下调用,将抛出 IllegalMonitorStateException,因为只有锁的持有者才能安全地修改等待条件,并通知所有等待线程

2.9 wait

2.9.1 wait(long timeout)

// 等待 timeout 毫秒之后自动醒来,或者靠唤醒(释放锁)
public final native void wait(long timeout) throws InterruptedException;
  • wait(long timeout) 方法使当前线程暂停并释放它持有的对象锁,进入等待状态,直到发生以下事件之一:
    • 被通知: 如果有其他线程对此对象调用了 notify()notifyAll(),当前线程可能被唤醒
    • 超时: 如果指定的等待时间结束,当前线程会自动唤醒
    • 被中断: 如果当前线程在等待期间被中断,它会抛出 InterruptedException
  • 虚假唤醒:指线程可能在没有任何明确通知的情况下唤醒。为了处理这种情况,等待通常应该放在一个循环中,以确保等待的条件确实得到了满足
  • 代码示例
Thread consumerThread = new Thread(() -> {
synchronized (sharedResource) {
while (!dataReady) {
try {
System.out.println("消费者正在等待数据准备好...");
// 如果在5000毫秒内生产者准备数据并调用 notify(), 它将被唤醒; 否则, 它将因超时而唤醒
sharedResource.wait(5000); // 等待通知或超时
if (dataReady) {
System.out.println("消费者拿到数据!");
} else {
System.out.println("消费者超时! 无可用数据");
}
} catch (InterruptedException e) {
System.out.println("消费者被中断");
}
}
}
});

2.9.2 wait(long timeout, int nanos)

// 至少等待 timeout 毫秒,nanos 是一个纳秒级的附加时间,用来微调 timeout 参数
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
// 检查传入的 timeout 参数是否小于0,如果是,则抛出 IllegalArgumentException 异常,因为时间不能是负数
throw new IllegalArgumentException("timeout value is negative");
}
// 检查 nanos 参数是否在0到999999纳秒之间,如果不是,则抛出 IllegalArgumentException 异常,因为纳秒值必须在这个范围内
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
// 调整超时时间: 如果纳秒参数大于0,则将毫秒超时时间 timeout 加 1,因为在 Java 中,等待时间的最小单位是毫秒,任何非零的纳秒数都至少会导致等待时间增加1毫秒
if (nanos > 0) {
timeout++;
}
// 调用重载的 wait(long timeout) 方法,传入调整后的毫秒数
wait(timeout);
}
  • 功能描述wait(long timeout, int nanos) 方法使得当前线程可以在释放对象锁的同时等待至多 timeout 毫秒加 nanos 纳秒。如果在此期间:
    • 被通知: 如果有其他线程对此对象调用了 notify()notifyAll(),当前线程可能被唤醒
    • 超时: 如果指定的等待时间结束,当前线程会自动唤醒
    • 被中断: 如果当前线程在等待期间被中断,它会抛出 InterruptedException
  • 方法作用:提供了比毫秒更细的时间控制单位,即纳秒,对于需要极高时间精度的应用场景非常重要

2.9.3 wait()

// 永不超时,需要靠唤醒(释放锁)
public final void wait() throws InterruptedException {
wait(0);
}
  • wait() 方法调用 wait(long timeout) 方法并传递 0 作为参数,表示允许当前线程放弃对象锁并无限期地等待,直到其他线程对此对象调用 notify()notifyAll() 方法唤醒它,或者当前线程被中断

2.10 finalize()

// 对象在被 GC 回收后执行的清理操作, 可能会引发 OOM(内存泄漏或内存溢出),建议使用 Java 9 引入的一个工具类 java.lang.ref.Cleaner 替代
protected void finalize() throws Throwable { }
  • 在 Java 早期,finalize() 是处理对象销毁前资源释放的主要方式,它允许在对象被垃圾收集器回收之前执行清理操作
  • 理论上,finalize() 方法中可以采取措施使对象再次可用(例如,重新赋予引用),但通常不推荐,因为会导致对象回收行为变得不可预测
  • 从 Java 9 开始,finalize() 方法已被标记为过时,因为它可能导致不稳定和效率低下的代码

3. 总结

3.1 方法概览

  • getClass():返回对象的运行时类。提供了动态获取类信息的能力,是 Java 反射机制的基础之一
  • hashCode():返回对象的哈希码,主要用于哈希表(如 HashMap)中。它需要与 equals() 方法保持一致,即相等的对象必须有相同的哈希码
  • equals(Object obj):检测某个对象是否等于当前对象。通常需要在自定义类中重写以实现逻辑上的 “相等”
  • clone():创建并返回此对象的一个副本。默认行为是浅拷贝,但可以被重写实现深拷贝
  • toString():返回对象的字符串表示,通常包含类名和哈希码的无符号十六进制表示。通常被重写以提供更多的实例信息
  • notify()notifyAll():用于唤醒在此对象监视器上等待的单个线程或所有线程。这些方法必须在同步块内调用,且调用者必须是对象锁的当前持有者
  • wait() 方法族(wait(), wait(long timeout), wait(long timeout, int nanos):使当前线程放弃对象锁并进入等待状态,直到被通知(notify/notifyAll)或中断。用于线程间的协调和通信
  • finalize():在垃圾收集器回收对象之前调用,用于清理资源。已被废弃并不推荐使用,因为它不可预测、效率低下且容易出错。建议使用 try-with-resourcesCleaner

3.2 归纳

  • 线程同步与通信wait(), notify(), 和 notifyAll() 是 Java 中实现线程间同步和通信的基本机制。这些方法在使用时需要特别注意同步块的设计,以避免死锁或过早通知等问题
  • 对象克隆clone() 方法提供了对象复制的能力,但需要注意浅拷贝与深拷贝的区别及其对应用的影响
  • 哈希与等价hashCode()equals() 方法定义了对象哈希存储和等价比较的标准,重写这些方法时必须遵循一定的约定,以保证它们的一致性
posted @   路有所思  阅读(144)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示