jdk源码(一):Object 一切类的根本!

一、概述

Object 类是位于java.lang包下面的类,是所有类的父类,是类层级的根,所有的对象,包括数组、也都实现了Object的方法。

二、源码分析

(1) registerNatives方法与静态代码块,源码如下:
private static native void registerNatives();
    static {
        registerNatives();
    }

说明:

  • native 关键字修饰的方法没有方法体, 这是java调用其他地方的接口的一个声明关键字,意思是这个方法不是java实现的,native关键字是与c语言联合开发的时候使用 ,为了能够快速执行代码。hashCode()方法也用到了,因为涉及内存,C语言操作比java快捷高效。 值得注意的是,native方法不能与abstract方法一起使用,因为native表示这些方法是有实现体的,但是abstract却表示这些方法是没有实现体的,那么两者矛盾,肯定也不能一起使用。

  • registerNatives()方法的作用是在类加载时,就主动将本地方法连接到调用方, 当Java程序需要调用本地方法时就可以直接调用,而不需要虚拟机再去定位并链接。 这样做的好处有以下4点:

    a. 通过registerNatives方法在类被加载的时候就主动将本地方法链接到调用方,比当方法被使用时再由虚拟机来定位和链接更方便有效。

    b. 如果本地方法在程序运行中更新了,可以通过调用registerNative方法进行更新。

    c. Java程序需要调用一个本地应用提供的方法时,因为虚拟机只会检索本地动态库,因而虚拟机是无法定位到本地方法实现的,这个时候就只能使用registerNatives()方法进行主动链接。

    d. 通过registerNatives()方法,在定义本地方法实现的时候,可以不遵守 JNI命名规范 。 JNI命名规范要求本地方法名由“包名”+“方法名”构成 。

(2) getClass()方法源码如下:
 public final native Class<?> getClass();

我们可以看到,getClass方法也是用native关键字修饰的,原因同上;同时还被final关键字修饰。final修饰的方法是不能被子类重写的,我们来详细分析以下这里的内容。

说明:

  • final修饰符在java中的作用有以下3点:

    a. final修饰的类,为最终类,该类不能被继承,如String 类。

    b. final修饰的方法可以被继承和重载,但是不能被重写。

    c. final修饰的变量是不能被修改的,是个常量。

此处就是为了防止任何继承类改变它的本来含义,希望此方法的行为在继承期间保持不变,而且不可被覆盖或是改写。

  • getClass()方法能够获取此Object的运行时类,是利用反射机制完成的。java中类的生命周期,会经历加载、连接(验证、准备、解析)、初始化、使用、卸载五个步骤。加载过程中会经历一个流程java->class-> 内存(生成Java.lang.Class文件)。生成的.class文件将会在反射中使用。如果有一个实例那么就可以通过实例的getClass()方法获取该对象的类型类,如果你知道一个类型,那么你可以使用.class()的方法获得该类型的类型类。
(3)hashCode()方法源码如下:
public native int hashCode();

hashCode()方法返回当前对象运行时的hash码,是用于支持散列表数据结构,因为散列表在进行数据存储时依赖hash码决定数据存储的逻辑位置。在程序运行中,无论什么情况下,相同的对象对应的hash码一定是相同的。但是不同的对象有可能会返回相同的hash码。那么其实也代表如果两个对象的hash码不一致,这两个对象一定是不同的。hashCode方法还具有以下3个特点:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。

  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。

  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode方法不要求一定生成不同的整数结果,为不相等的对象生成不同整数结果可以提高哈希表的性能。

hashCode()方法有时也会返回相同的hash值,就是我们所说的hash冲突。因为尽管虚拟机在运行过程中,不同的对象的地址一定是不同的,但是由于hashcode需要固定25位或者31位,那么就导致真正的hashcode值需要在对象地址上做一定的操作。从而将一个大范围区间的值映射到一个小范围区间的值(hashcode的计算过程),这样的操作必定会导致一部分数据的计算结果会重复。

(4) equals()方法源码如下:
public boolean equals(Object obj) {
    return (this == obj);
}

Object中的equals方法默认比较当前对象的引用,是直接判断this和obj本身的值是否相等,即用来判断调用equals的对象和形参obj所引用的对象是否是同一对象,所谓同一对象就是指内存中同一块存储单元,如果this和obj指向的是同一块内存对象,则返回true,如果this和obj指向的不是同一块内存,则返回false,注意:即便是内容完全相等的两块不同的内存对象,也返回false。

值得注意的是,String类重写了此方法,源代码如下:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n– != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }
            return true;
        }
    }
    return false;
}
(5) toString()方法源码如下:
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

说明:

  • getName() 以String形式返回类对象的名称(包括包名) 。

  • Integer.toHexString(hashCode()) 以对象的哈希码为参数,以16进制无符号整数形式返回此哈希码的字符串表示形式。

(6) 我们进入到toHexString源码中,如下:
public static String toHexString(int i) {
    return toUnsignedString0(i, 4);
}

private static String toUnsignedString0(int val, int shift) {
    // assert shift > 0 && shift <=5 : "Illegal shift value";
    //为了计算val值对应的二进制数中除去首部0的个数后剩下的有效位数mag。
    //Integer.SIZE: int在jvm中占4个字节,共32位;
    //首部0的个数:是指从左边第一个位置开始累加0的个数,一直加到第一个非零值;
    //方法numberOfLeadingZeros() 就是计算首部0的个数。
    int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val); 
    //计算 i 的有效二进制的位数所需要占的字符数(即对应的16进制的有效位数),toString()方法是以16进制返回,这里就以16进制来解释。
    int chars = Math.max(((mag + (shift - 1)) / shift), 1);
    char[] buf = new char[chars];
    //将val值以16进制数形式存进buf数组中。
    formatUnsignedInt(val, shift, buf, 0, chars);
 
    // Use special constructor which takes over "buf".
    return new String(buf, true);
}
(7) 我们再进入到numberOfLeadingZeros()方法中查看源码,如下:
public static int numberOfLeadingZeros(int i) {
    // HD, Figure 5-6
    if (i == 0)
        return 32;
    // 初始首部0的个数:1 假定最高位有一个0,最后的时候会根据最高位是否位零补偿返回
    int n = 1;
    // 下面的代码主要是为了找出左边第一个非零值的位置
    // 采用二分查找的方法,首先将 i 无符号右移16位后,有两种情况(a, b):
    // a. 右移后 = 0,则第一个非零值出现在低16位,那么 i 首位至少有16个0(n=17),
    // 同时将 i 左移16位后赋值给自己(将低16位移到了高16位,这样可以使两种保持同一的状态进行后续判断);
    // b. 右移后!=0,则第一个非零值出现在高16位(n=1),继续在高16位中寻找;
    // 将高16位继续分为 高8位和低8位进行判断,一直二分到还有两位的时候;
    // 最后 i 无符号右移31位(结果要么是0要么是1),如果右移后为0,说明此时的最高位为0,无需补偿,直接返回 n;
    // 如果右移后是1,则说明最高位不为0,需补偿,返回 n-1;
    if (i >>> 16 == 0) { n += 16; i <<= 16; }
    if (i >>> 24 == 0) { n +=  8; i <<=  8; }
    if (i >>> 28 == 0) { n +=  4; i <<=  4; }
    if (i >>> 30 == 0) { n +=  2; i <<=  2; }
    n -= i >>> 31;
    return n;
}

我们可以看出, 源码应用了二分查找,先把32位整形分为高16位和低16位查找非零数,在对高16位进行或低16位进行二分,以此类推,直到找到左边第一个非零值的位置。

(8) 最后我们在进入到formatUnsignedInt()方法中,源码如下:

 static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
    int charPos = len;
    int radix = 1 << shift;
    int mask = radix - 1;
    do {
        buf[offset + --charPos] = Integer.digits[val & mask];
        val >>>= shift;
    } while (val != 0 && charPos > 0);

    return charPos;
}

/**
 * All possible chars for representing a number as a String
 */
final static char[] digits = {
    '0' , '1' , '2' , '3' , '4' , '5' ,
    '6' , '7' , '8' , '9' , 'a' , 'b' ,
    'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
    'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
    'o' , 'p' , 'q' , 'r' , 's' , 't' ,
    'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};

源码中,先进行按位与运算,算出val的二进制中低四位对应的10进制的值,在数组中获取对应的16进制值,存进buf字符数组中;后将val无符号右移4位,保持在最低四位与mask进行按位与运算,在数组中获取对应的16进制值,存进buf字符数组中,以此循环,直到val=0或者charPos<=0后退出循环。digits 数组中表示 16 进制的是前 16 个,数组下标最大为 15, Integer.digits[val & mask] 保证了每次从最低四位开始匹配对应的 16 进制数。总结起来步骤如下:

a. 计算 hashCode 对应的有效二进制位数 n

b. n 位二进制数对应的 16 进制数的个数,即对应的所要占据的字符数

c. 将 n 位二进制数转换为 16 进制数并存入字符数组中

d. 最后返回 16 进制无符号整数的字符串

(9) clone()方法源码如下:

protected native Object clone() throws CloneNotSupportedException;

说明:

  • 如果一个类没有子类,那么这个类的protected方法就只能在这个类所在包下的类中被调用,如果这个方法有子类,那么这个子类继承父类的protected方法也就只能在这个子类内部使用。

  • 一个类想要使用clone()方法的话,就要先实现克隆接口Cloneable。Cloneable接口进去发现,没有任何内容,这个接口是一个JVM标记接口。如果在没有实现 Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupportedException异常。

  • 标记接口是计算机科学中的一种设计思路,用于给那些面向对象的编程语言描述对象。因为编程语言本身并不支持为类维护元数据,而标记接口可以用作描述类的元数据,弥补了这个功能上的缺失。对于实现了标记接口的类,我们就可以在运行时通过反射机制去获取元数据。

  • 浅克隆是在克隆时,基本数据类型直接拷贝,而引用类型只能拷贝引用地址。

  • 深克隆是重写clone方法,每次调用时, 先克隆一下引用类型,把克隆后的引用类型设置给克隆的对象

(10) finalize()方法源码如下:
 protected void finalize() thirows Throwable { }

此方法是垃圾回收时调用的方法,存在以下两种情况可能会调用:

  • 对象置为空后,JVM内存不充足时会回收

  • 可以主动建议JVM回收Runtime.getRunTime().gc()

(11) notify()方法

学习多线程时详细介绍

(12) notifyAll()方法

学习多线程时详细介绍

(13) wait()方法

学习多线程时详细介绍

三、总结

Object类作为所有类的根类,学好它的源码是非常必要的,也能带给我们更多的思考。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342