我的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->calss-> 內存(生成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()方法中,源碼如下:

        源碼中,先進行按位與運算,算出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類作爲所有類的根類,學好它的源碼是非常必要的,也能帶給我們更多的思考。敬請期待《我的jdk源碼(二):String 一個特殊而強大的類!》。更多精彩內容,敬請掃描下方二維碼,關注我的微信公衆號【Java覺淺】,獲取第一時間更新哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章