JDK源码系列(2)-Object类

简介: JDK源码系列(2)-Object类

 引言

我们都知道,在Java中,Object是所有类的超类,所有的类其实都是隐含继承自Object类的,所以extends Object默认是不用写的,当然你写了也不会错。所有的类都可以使用Object类中的方法,下面我们按源码的顺序分别来介绍。

Object类中的常用方法有:toString(),getClass(),hashCode(),equals(),clone(),finalize(),其中,定义为final类型,不能重写的方法:getClass(),notify(),notifyAll(),wait()。

Object类中有部分方法是由native关键字修饰的,这代表使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。所以native关键字的函数都是操作系统实现的。

1.registerNatives方法

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

image.gif

native修饰的方法,通常有c或c++实现。这个方法表示的是在类被加载时,调用 registerNatives()方法进行一些跟系统有关的方法调用,而这个方法的实现就在java.dll中(里面会根据不同系统来执行不同的底层操作)。

下面是JDK1.6中关联的C语言代码(来自OpenJDK 6):

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};
JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

image.gif

2.getClass方法

public final native Class<?> getClass();

image.gif

getClass返回运行时的类类型,final修饰不能被子类继承,native表示也不是由Java实现的。一般与getName方法一起使用。它能获取类的定义信息,然后通过反射的方式获取类的元信息和方法信息,包括函数和字段。

3.hashCode方法

public native int hashCode();

image.gif

hashCode被native修饰为本地方法,该方法返回该对象的哈希码值,重写了equals方法一般都要重写hashCode方法。注意下面几点:

    1. hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
    2. 如果两个对象相同,就是满足于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
    3. 如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面第2点;
    4. 两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

    4.equals方法

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

    image.gif

    equals直接判断this和obj的值是否相等,即判断两个对象是否为同一个对象,通过==判断,表示只有当两个对象的内存地址值是相同的他们才是同一个对象。如果你希望两个不同地址但内容相同的对象在进行equals方法比较时,就需要对equals方法进行重写。比如String的equals就重写了,分为两个逻辑,先比较==,如果相等直接返回true;如果不等接下来再把字符串分成字符数组,一个一个字符进行比较,如果字符都相等,也返回true,代码如下:

    //被String重写的equals方法
        public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }

    image.gif

    5.clone方法

    protected native Object clone() throws CloneNotSupportedException;

    image.gif

    clone被native修饰为本地方法,protected保护方法,实现对象的浅拷贝,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

    在某些场景中,我们需要获取到一个对象的拷贝用于某些处理。这时候就可以用到Java中的Object.clone方法进行对象复制,得到一个一模一样的新对象。但是在实际使用过程中会发现:当对象中含有可变的引用类型属性时,在复制得到的新对象对该引用类型属性内容进行修改,原始对象响应的属性内容也会发生变化,这就是"浅拷贝"的现象。表明浅拷贝在拷贝引用类型属性时,只拷贝了该属性的引用。如果希望拷贝后的对象是完全的新对象,那就需要重写clone方法或者采用序列化的方式进行对象的复制。

    6.toString方法

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

    image.gif

    toString方法返回一个字符串,类名+@+哈希值的16进制无符号整数形式。该方法用得比较多,一般子类都有覆盖。

    7.notify方法

    public final native void notify();

    image.gif

    notify被native修饰为本地方法,不可被重写,该方法唤醒在该对象上等待的某个线程。

    8.notifyAll方法

    public final native void notifyAll();

    image.gif

    notifyAll被native修饰为本地方法,不可被重写,该方法唤醒在该对象上等待的所有线程。

    9.wait(long timeout)方法

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

    image.gif

    wait被native修饰为本地方法,不可被重写,调用该方法后当前线程进入睡眠状态,让当前线程等待,使线程等待指定长的时间(毫秒)。在其他线程调用了notify或是notifyAll方法或是达到指定的时长,该线程就可以被调度。其他线程调用了interrupt中断该线程,就抛出一个InterruptedException异常。

    10.wait(long timeout, int nanos)方法

    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 >= 500000 || (nanos != 0 && timeout == 0)) {
                timeout++;
            }
            wait(timeout);
        }

    image.gif

    wait不可被重写,和wait(long timeout)方法本质相同,允许更好地控制在放弃之前等待通知的时间,实时量,以毫微秒计算,计算公式如下:1000000*timeout+nanos,特别是wait(0, 0) 表示和wait(0)相同,当前线程必须拥有该对象的监视器。

    11.wait方法

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

    image.gif

    实际上调用的是wait(long timeout)方法只不过timeout传的是0,只有调用了notify或是notifyAll方法,线程才会被唤醒。

    12.finalize方法

    protected void finalize() throws Throwable {}

    image.gif

    该方法是protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法,它是一个空实现方法。该方法与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性。调用这个方法只是建议gc去调用这个方法,但是还是不能保证一定会被调用。

    finalize的执行周期:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

    参考资料:

      1. JDK源码学习--Object类_书呆子Eric的博客-CSDN博客
      2. JDK源码学习_书呆子Eric的博客-CSDN博客_jdk源码怎么学
      3. Object 的 registerNatives() 方法是用来做什么的 能解释的通俗一点吗-Java-CSDN问答
      相关文章
      |
      2月前
      |
      Java Linux
      java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
      本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
      64 2
      |
      14天前
      |
      存储 Java 程序员
      Java基础的灵魂——Object类方法详解(社招面试不踩坑)
      本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
      54 4
      |
      1月前
      |
      Java
      Java Object 类详解
      在 Java 中,`Object` 类是所有类的根类,每个 Java 类都直接或间接继承自 `Object`。作为所有类的超类,`Object` 定义了若干基本方法,如 `equals`、`hashCode`、`toString` 等,这些方法在所有对象中均可使用。通过重写这些方法,可以实现基于内容的比较、生成有意义的字符串表示以及确保哈希码的一致性。此外,`Object` 还提供了 `clone`、`getClass`、`notify`、`notifyAll` 和 `wait` 等方法,支持对象克隆、反射机制及线程同步。理解和重写这些方法有助于提升 Java 代码的可读性和可维护性。
      |
      3月前
      |
      Java
      【Java基础面试二十】、介绍一下Object类中的方法
      这篇文章介绍了Java中Object类的常用方法,包括`getClass()`、`equals()`、`hashCode()`、`toString()`、`wait()`、`notify()`、`notifyAll()`和`clone()`,并提到了不推荐使用的`finalize()`方法。
      【Java基础面试二十】、介绍一下Object类中的方法
      |
      2月前
      |
      Java API 开发者
      【Java字节码操控新篇章】JDK 22类文件API预览:解锁Java底层的无限可能!
      【9月更文挑战第6天】JDK 22的类文件API为Java开发者们打开了一扇通往Java底层世界的大门。通过这个API,我们可以更加深入地理解Java程序的工作原理,实现更加灵活和强大的功能。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来!
      |
      2月前
      |
      Java API 开发者
      【Java字节码的掌控者】JDK 22类文件API:解锁Java深层次的奥秘,赋能开发者无限可能!
      【9月更文挑战第8天】JDK 22类文件API的引入,为Java开发者们打开了一扇通往Java字节码操控新世界的大门。通过这个API,我们可以更加深入地理解Java程序的底层行为,实现更加高效、可靠和创新的Java应用。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来,并积极探索类文件API带来的无限可能!
      |
      2月前
      |
      Python
      类与面向对象编程(Object-Oriented Programming, OOP)
      类与面向对象编程(Object-Oriented Programming, OOP)
      |
      3月前
      |
      算法 安全 Java
      深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
      【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
      54 2
      |
      3月前
      |
      存储 Java
      【Java集合类面试七】、 JDK7和JDK8中的HashMap有什么区别?
      JDK7中的HashMap使用数组加链表解决冲突,而JDK8增加了红黑树结构以优化链表过长时的性能,提高查找效率。
      |
      3月前
      |
      Oracle 安全 Java
      JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
      JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的