走进Java Object类(一篇读懂源码)

本文详细解读了Java中的Object类,包括equals和hashCode方法的重要性,以及wait,notify和notifyAll方法在多线程同步中的作用。重点介绍了这些方法在对象继承结构和线程通信中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Object对象

  • 而Object就是这些对象的最高级别的,所有的Java对象都隐式地继承了Object对象(不用显示写extends继承)
  • 所有的Java对象都拥有Object默认的方法

Object.class源码:

package java.lang;   //Object类的位置
//object类
public class Object {

    //这是一个声明,表明了存在一个私有的静态本地方法 registerNatives
    private static native void registerNatives();
    //静态初始化块,它在类被加载时执行.
    //调用了 registerNatives() 方法,这个方法是本地方法,
    //在 Java 代码中没有具体实现,而是由本地代码来实现。
    static {
        registerNatives();
    }

    //getClass()方法返回的是对象的实际类型的Class对象,在运行时可以用于获取对象所属的类的相关信息。
    public final native Class<?> getClass();

    //hashCode()方法返回的是对象的哈希码值
    //注:关键字"native"表示该方法的实现是由本地代码(如C或C++)实现的,而不是由Java代码实现的
    //hashCode()方法的一般规范:
    //1.如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象的hashCode()方法应该返回相同的整数结果。
    //2.如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象的hashCode()方法不要求返回不同的整数结果,但是如果返回不同的结果,可能会提高哈希表的性能。
    //3.尽可能地,对于不同的对象,hashCode()方法应该返回不同的整数结果,这通常通过将对象的内部地址转换为整数来实现,但是Java编程语言并不要求使用这种方式来实现。
    public native int hashCode();

    //equals 方法用于判断当前对象是否与其他对象相等
    //equals 方法应当满足的几条条件:
    //1.reflexive(自反性):对于任意非空引用值 x,x.equals(x) 应当返回 true。
    //2.symmetric(对称性):对于任意非空引用值 x 和 y,x.equals(y) 应当返回 true 当且仅当 y.equals(x) 返回 true。
    //3.transitive(传递性):对于任意非空引用值 x、y 和 z,如果 x.equals(y) 返回 true 且 y.equals(z) 返回 true,那么 x.equals(z) 应当返回 true。
    //4.consistent(一致性):对于任意非空引用值 x 和 y,多次调用 x.equals(y) 应当一致地返回 true 或 false,前提是在进行 equals 比较时所用的信息没有被修改。
    //5.对于任意非空引用值 x,x.equals(null) 应当返回 false。
    public boolean equals(Object obj) {
        return (this == obj);
    }

    //clone()方法用于创建并返回一个对象的拷贝
    //调用x.clone()后得到的新对象应该满足以下条件:
    //x.clone() != x,即返回的对象是原对象的拷贝,二者不相同。
    //x.clone().getClass() == x.getClass(),即返回的对象和原对象属于同一个类。
    //通常情况下,x.clone().equals(x)应该为true,但这不是绝对的要求。
    //返回类型:Object 类中的 clone() 方法返回一个对象的拷贝,因此其返回类型为 Object。由于 Java 不支持多返回类型,因此实际使用时通常需要将返回的对象转换为适当的类型。
    //克隆能力:默认情况下,clone() 方法提供了浅拷贝,即它只会复制对象的字段值。如果对象的字段包含引用类型,则原始对象和克隆对象将共享相同的引用,这可能导致意外的行为。如果需要实现深拷贝,即完全复制对象及其所有引用的内容,就需要在具体类中重写 clone() 方法以实现深拷贝逻辑。
    //异常处理:clone() 方法的实现可能会抛出 CloneNotSupportedException 异常,如果类不支持克隆操作,则可以通过在类中覆盖 clone() 方法,并在方法内部抛出 CloneNotSupportedException 来禁止该类的实例被克隆。
    //使用注意事项:由于 clone() 方法是 Object 类的保护方法,因此直接在自定义类中调用该方法是不安全的。为了使用 clone() 方法,通常需要满足以下两个条件:一是目标类必须实现 Cloneable 接口,这个接口是一个标记接口,没有定义任何方法,但是它是用来标识该类支持克隆;二是在目标类中覆盖 clone() 方法,并且在其中可以调用父类的 clone() 方法得到一个浅拷贝,然后进行必要的深拷贝操作。
    protected native Object clone() throws CloneNotSupportedException;
    
    //toString() 方法返回一个代表对象的字符串表示
    //默认实现会返回由对象的类名和对象的哈希码组成的字符串。具体来说,返回值是类名后跟字符 '@',再跟着对象的哈希码的无符号十六进制表示。
    //在实际开发中,我们通常需要根据对象的具体属性来重写 toString() 方法,以便能够更清晰地表示对象的状态和信息。这样做可以方便调试和日志输出,提高代码的可读性。
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    //notify() 方法用于唤醒等待在对象监视器上的单个线程。
    //如果有任何线程在该对象上等待,会选择其中一个线程进行唤醒。被唤醒的线程在获得了对象锁之后才能继续执行。
    //notify() 方法的工作原理:
    //1.被唤醒的线程即将继续执行,但需要等待当前线程释放该对象的锁。
    //2.被唤醒的线程与其他正在竞争该对象锁的线程一样地竞争,不存在特权或劣势,哪一个线程能够获得对象锁是不确定的。
    //notify() 方法的前提条件:
    //只能由拥有该对象监视器的线程来调用该方法。而线程可以通过三种方式之一成为对象监视器的拥有者:执行该对象的同步实例方法、在同步语句块中同步该对象、对于 Class 类型的对象,执行该类的同步静态方法。
    //可能抛出的异常情况:
    //如果当前线程不是该对象监视器的拥有者,则会抛出 IllegalMonitorStateException 异常。
    public final native void notify();

	//notifyAll() 方法唤醒等待在该对象监视器上的所有线程。这些线程通过调用对象的 wait 方法来等待。
	//被唤醒的线程在获得了对象锁之后才能继续执行,与其他竞争锁的线程一样,它们没有特权或劣势。
	//只有拥有对象监视器的线程才能调用此方法。在描述如何成为对象监视器的拥有者时,提到了 notify 方法。
    //如果当前线程不是该对象监视器的拥有者,则会抛出 IllegalMonitorStateException 异常。
    public final native void notifyAll();

    //wait方法是用于使当前线程等待,直到其他线程调用notify()或notifyAll()方法,或者指定的时间过去。
    //wait方法的作用:
    //1.当前线程必须拥有该对象的监视器(即在同步块或同步方法中调用wait方法)。
    //2.调用wait方法后,当前线程将自己放入该对象的等待集合中,并且释放对该对象的所有同步锁。
    //-线程处于不可调度状态,等待以下四种情况之一发生:
    //---其他线程调用该对象的notify方法,并且当前线程恰好被选择为唤醒线程。
    //---其他线程调用该对象的notifyAll方法。
    //---其他线程中断当前线程。
    //---指定的等待时间过去了。如果timeout为0,则不考虑实际时间,线程只会等待直到被通知。
    //3.在某些情况下,线程可能会在没有被通知、中断或超时的情况下唤醒,这种情况称为"虚假唤醒"。虽然这在实践中很少发生,但应用程序必须通过测试应该唤醒线程的条件并在条件不满足时继续等待来防止它。也就是说,等待应始终发生在循环中。
    //4.如果在当前线程等待期间,任何线程在等待前或等待中对其进行了中断,则会抛出InterruptedException。此异常直到恢复此对象的锁定状态后才抛出。
    public final native void wait(long timeout) throws InterruptedException;


    //带有超时时间的wait方法。
    //1.当前线程必须拥有该对象的监视器(即在同步块或者同步方法中调用wait方法)。
	//2.调用这个带有超时参数的wait方法后,当前线程会等待直到满足以下条件之一:
	//--1.其他线程调用该对象的notify方法或notifyAll方法。
	//--2.其他线程中断了当前线程。
	//--3.指定的等待时间已经过去。
	//3.方法中对超时时间和纳秒数的合法性进行了检查,如果不合法则会抛出IllegalArgumentException异常。
	//4.如果在等待过程中线程被中断,则会抛出InterruptedException异常。
	//5.在等待期间,中断和虚假唤醒都是可能发生的,因此建议总是在循环中使用wait方法。
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

    //wait() 方法使当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法。
    public final void wait() throws InterruptedException {
        wait(0);
    }



    
    //1.finalize() 方法在垃圾回收器确定没有更多引用指向该对象时被垃圾回收器调用。
    //1.子类可以重写 finalize() 方法来处理系统资源的清理或执行其他清理操作。
    //3.finalize() 的一般约定是,在 Java™ 虚拟机确定没有任何方式可以通过任何尚未终止的线程访问该对象时,会调用此方法。唯一的例外是其他已准备好被终结的对象或类的终结动作导致此对象被访问。
    //--finalize() 方法可以执行任何操作,包括将该对象重新提供给其他线程;不过,finalize() 方法通常用于在对象被丢弃之前执行清理操作。
    //--例如,表示输入/输出连接的对象的 finalize() 方法可能会在对象被永久丢弃之前执行显式的I/O事务来断开连接。
    //Object 类的 finalize() 方法不执行任何特殊操作,只是正常返回。Object 的子类可以重写此定义。
    //Java 编程语言不保证哪个线程会调用任何给定对象的 finalize() 方法。但是,保证调用 finalize() 的线程在调用时不持有任何对用户可见的同步锁。
    //--如果 finalize() 方法抛出未捕获的异常,则忽略该异常,并终止该对象的终结过程。
    //在调用对象的 finalize() 方法之后,除非 Java 虚拟机再次确定没有任何方式可以通过任何尚未终止的线程访问该对象(包括其他已准备好进行终结的对象或类的可能动作),否则不会采取进一步的操作,此时该对象可能被丢弃。
    //Java 虚拟机对任何给定对象只调用一次 finalize() 方法。
    //finalize() 方法抛出的任何异常都会导致该对象的终结过程停止,但其他情况下会被忽略。
    protected void finalize() throws Throwable { }

}

equals和hashCode方法

要点:

  • 重写equals()方法,就必须重写hashCode()的方法
  • hashCode()方法对底层是散列表的对象有提升性能的功能
  • 同一个对象(如果该对象没有被修改过):那么重复调用hashCode()那么返回的int是相同的!
  • hashCode()方法默认是由对象的地址转换而来的

wait和notify方法

  • 无论是wait、notify还是notifyAll()都需要由监听器对象(锁对象)来进行调用
    • 简单来说:他们都是在同步代码块中调用的,否则会抛出异常!
  • notify()唤醒的是在等待队列的某个线程(不确定会唤醒哪个),notifyAll()唤醒的是等待队列所有线程
  • 导致wait()的线程被唤醒可以有4种情况
    • 该线程被中断
    • wait()时间到了
    • 被notify()唤醒
    • 被notifyAll()唤醒
  • 调用wait()的线程会释放掉锁
为什么wait和notify在Object方法上?

wait()和notify()是Java给我们提供线程之间通信的API,那么既然是线程的东西,为什么是在Object类上定义,而不是在Thread类上定义?

因为我们的锁是对象锁,每个对象都可以成为锁。让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。锁对象是任意的,所以这些方法必须定义在Object类中。

notify方法调用后,会发生什么?

notify方法调用后,被唤醒的线程不会立刻获得锁对象。而是等待notify的synchronized代码块执行完之后才会获得锁对象。

sleep和wait有什么区别?

Thread.sleep()和Object.wait()二者都可以暂停当前线程,释放CPU控制权。

区别:Object.wait()在释放CPU同时,释放了对象锁的控制。

Thread.sleep()没有对锁释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘子编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值