1.2、JDK 源码分析 - Object类

原创
2022/06/09 15:38
阅读数 170

摘要

本博文主要介绍Object类中的常见函数用法以及其作用,Object类中主要有以下方法:

registerNatives()、getClass()、hashCode()、equals()、clone()、toString()、notify()、notifyAll()、wait()、finalize();

其中toString()、getClass()、equals是其中最重要的方法。Object类中的getClass()、notify()、notifyAll()、wait()等方法被定义为final类型,因此不能重写。

一、registerNatives() 方法

Java代码中,不光Object,在System、Class、ClassLoader、Unsafe等类中都能找到下面这段代码。

private static native void registerNatives();
static {
   registerNatives();
}

顾名思义,这段代码是当类加载时执行的静态代码块,进行本地方法注册。

Java程序有两种方法:

  • Java方法: Java方法是由Java语言编写,编译成字节码,存储在class文件中。Java方法是平台无关的,通过JVM加载字节码运行。

  • 本地方法: 本地方法在Java类中定义,用native进行修饰,且只有方法定义,没有方法实现。本地方法是由其他语言(比如C,C++,或者汇编)编写,编译成处理器相关的机器代码,并保存在动态连接库中,Java类不需要实现这些方法。当Java程序调用本地方法时,JVM通过加载本地方法的动态连接库,找到对应方法并执行相关代码。

registerNatives方法到底注册了哪些方法呢?

其实,在Object类中除了registerNatives方法外,还有getClass()、hashCode()、clone()、notify()、notifyAll()、wait()等本地方法。registerNatives()注册的方法就是该类所包含的除了registerNatives()方法以外的所有本地方法。

为什么要注册本地方法呢?

Java程序调用一个本地方法时,会执行两个步骤:

第一,将包含本地方法实现的动态文件加载进内存;

第二,JVM在加载的动态文件中定位并连接该本地方法的具体实现,然后执行该本地方法代码。registerNatives()方法的作用就是将Java代码中定义的本地方法和本地方法的实现连接起来,当Java程序调用一个本地方法时,就可以直接执行本地方法代码,不需要JVM再去定位并连接。这样做有三点好处:

  • 通过registerNatives方法在 类被加载 的时候就主动将本地方法连接到具体实现,比当本地方法被调用时再由JVM去定位和连接更方便快捷;

  • 本地方法是平台相关的,如果本地方法有变动,可以调用registerNatives方法进行更新;

  • 因为JVM只会检索本地的方法库,无法直接定位到本地方法的具体实现,所以就需要通过registerNatives()方法进行注册,主动关联动态连接库中的方法实现。

如何实现自己的本地方法库调用?

此时,就需要用到JAVA语言的JNI(Java Native Interface)、JNA(Java Native Access )技术;本篇文章暂时不深入去讲解JNI和JNA的使用,后续博主会专门开对应的博文来讲解相关技术核心!

二、getClass()方法

public final native Class<?> getClass();

返回此Object的运行时类类型。不可重写,要调用的话,一般和getName()联合使用,如getClass().getName();

三、hashCode()方法

public native int hashCode();

返回该对象的哈希码值,该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

四、equals()方法

public boolean equals(Object obj) {
   return (this == obj);
}

Object中的equals方法是直接判断this和obj本身的值是否相等,即用来判断调用equals的对象和形参obj所引用的对象是否是同一对象;所谓同一对象就是指内存中同一块存储单元,如果this和obj指向的同一块内存对象,则返回true;否则,返回false。

注意:即便是内容完全相等的两块不同的内存对象,也返回false。 如果是同一块内存,则object中的equals方法返回true;否则,返回false 如果希望不同内存但相同内容的两个对象equals时返回true,则我们需要重写父类的equal方法

五、clone()方法

clone方法JAVA的设计模式-原型模式的重要组成部分;另外, JDK规定能被clone的对象还需要实现Cloneable接口来标记自身是可以被克隆的!

 protected native Object clone() throws CloneNotSupportedException;

实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里将传递进来的参数改变,这时就需要在类中复写clone方法(实现深复制)。创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。

注意 clone()方法默认只拷贝对象基本的数据类型属性,对于对象类型的属性,它默认只是将对象类型属性的引用地址赋值给新的克隆对象的对应属性;要实现深度拷贝有几种方式,1、对象类也实现clone方法(),并且在拷贝对象的clone方法里面,手动去调用对象类型属性的clone方法;2、使用序列化、反序列化的方式去实现深度拷贝;

六、toString()方法

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。该方法用得比较多,一般子类都有覆盖。

七、wait()方法

public final native void wait(long timeout) throws InterruptedException;

public final void wait() throws InterruptedException {
        wait(0);
 }

 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方法就是使当前线程等待,当前线程必须是该对象的拥有者(具有该对象的锁)。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

(1)其他线程调用了该对象的notify方法。 (2)其他线程调用了该对象的notifyAll方法。 (3)其他线程调用了interrupt中断该线程。 (4)时间间隔到了。 此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

八、notify()方法

 public final native void notify();

随机唤醒正在等待对象监视器的一个线程。

九、notifyAll()方法

public final native void notifyAll();

唤醒正在等待对象监视器的所有线程。

十、finalize()方法

protected void finalize() throws Throwable { }

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。Java允许在类中定义一个名为finalize()的方法。它的工作原理是:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法。并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

关于垃圾回收,有三点需要记住:

1、对象可能不被垃圾回收。只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。 2、垃圾回收并不等于“析构”。 3、垃圾回收只与内存有关。使用垃圾回收的唯一原因是为了回收程序不再使用的内存。 finalize()的用途:

无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对finalize()的需求限制到一种特殊情况,即通过某种创建对象方式以外的方式为对象分配了存储空间。 不过这种情况一般发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。

知识扩展

1、 wait()、notify()、notifyAll() 都不属于Thread类,属于Object基础类,每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁。

2、 当需要调用wait( ),notify( ),notifyAll( )的时候,一定都要在synchronized里面,不然会报 IllegalMonitorStateException 异常,可以这样理解,在synchronized(object) {}里面的代码才能获取到对象的锁。

3、 while循环里而不是if语句下使用wait,这样,会在线程暂停恢复后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知

4、 调用obj.wait()释放了obj的锁,程序就会阻塞在这里;如果后面被notify()或者notifyAll()方法呼唤醒了之后,那么程序会接着调用obj.wait()后面的程序。

5、 notify()随机唤醒等待获取锁资源的单个线程,notifyAll( )唤醒等待获取锁资源的所有线程

6、 当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,其它线程仍无法获得obj锁,直到调用线程退出synchronized块或者在原有的obj调用wait释放锁,其它的线程才能起来

7、 wait()、notify()、notifyAll()可用于实现生产者-消费者模式

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部