1. 程式人生 > >JDK1.8原始碼閱讀——Object類

JDK1.8原始碼閱讀——Object類

一、Object類的結構

    上圖為Object類的結構樹,由此可以清晰的看到整個Object的架構。其中個人經過搜尋、日常開發的總結,認為Object、clone、equals(Object)、hashCode、getClass、toString這幾個方法相對重要(僅屬個人意見,如有不同之見,歡迎討論)。可能有人認為notify、wait等執行緒有關的方法也很重要,但是從個人角度出發,我認為這些方法更應該放線上程裡去研究和討論。

二、native方法介紹

    我們都知道,Java的底層是通過C、C++等語言實現的,那麼Java是如何區分這些方法並能準確地去呼叫的呢?

2.1 native關鍵字

    在Java中,如果一個方法使用native關鍵字來修飾,即表明該方法並不是由Java實現的,它是由non-java即C、C++負責實現。其具體的實現方法被編譯在了dll檔案中,由Java呼叫。由native修飾的方法有:


2.2  registerNatives()

    registerNatives(),顧名思義,註冊native修飾的方法,其作用是將C、C++等方法對映到Java中由native修飾的方法,這也是Java為何能做到準確地去呼叫non-java方法。其原始碼如下:

private static native void registerNatives();
    可以看到,這裡並沒有執行此方法。Java使用的是靜態程式碼塊去執行registerNatives(),原始碼如下:

static {
    registerNatives();
}
2.3   clone()

protected native Object clone() throws CloneNotSupportedException;
    通過上述原始碼,我們知道,clone不是Java原生的方法,且Object提供的複製是淺複製,不是深度複製。

    淺複製是指只複製物件的引用,而深度複製則是將原來複制的物件完完全全的複製出來,此時被複制的物件與複製出來的物件已經沒有任何關係,概括起來就是,淺複製複製引用與值,引用與值都不變;深度複製複製值,引用變值不變。

    可以看到,clone方法顯式丟擲不支援複製的異常,這說明,實現物件的複製是有條件的。

    1)  方法由protected修飾,說明若要實現複製,需要繼承Object(預設都是繼承Object的...)

    2)返回型別為Object,表明若要得到我們想要複製的結果需要進行型別轉換

    3)實現Cloneable介面,否則會丟擲不支援複製的異常

    以下為一段測試clone的程式碼:

class Son { 
    
    private Integer sonAge;
    
    private String sonName;
 
    public Son(Integer sonAge, String sonName) {
        super();
        this.sonAge = sonAge;
        this.sonName = sonName;
    }
 
    public Son() {
        super();
    }
 
    public Integer getSonAge() {
        return sonAge;
    }
 
    public void setSonAge(Integer sonAge) {
        this.sonAge = sonAge;
    }
 
    public String getSonName() {
        return sonName;
    }
 
    public void setSonName(String sonName) {
        this.sonName = sonName;
    }
}
public class Parent {
 
    public static void main(String[] args) throws CloneNotSupportedException {
        
        Son son = new Son(10,"清然");
        
        Parent parent = new Parent(20,"安然",son);
        Parent temp1 = parent.clone();
    }
    
    private Integer age;
    
    private String name;
    
    private Son son;
    
    public Parent(Integer age, String name, Son son) {
        super();
        this.age = age;
        this.name = name;
        this.son = son;
    }
 
    public Parent() {
        super();
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Son getSon() {
        return son;
    }
 
    public void setSon(Son son) {
        this.son = son;
    }
    
}
   此時編譯器提示需要進行型別轉換:

   完成型別轉換後,提示需要對異常進行處理,即CloneNotSupportedException:

    將異常丟擲後,沒有發現任何編譯錯誤,我們執行main方法,控制檯出現如下錯誤:

Exception in thread "main" java.lang.CloneNotSupportedException: jdkreader.java.lang.object.Parent
    at java.lang.Object.clone(Native Method)
    at jdkreader.java.lang.object.Parent.main(Parent.java:16)
   異常顯示Parent並不支援clone,此時我們需要實現Cloneable介面:

public class Parent implements Cloneable
   我們可以看一下複製後的兩個物件的關係:

Son son = new Son(10,"清然");
        
Parent parent = new Parent(20,"安然",son);
Parent temp1 = (Parent) parent.clone();
Parent temp2 = (Parent) parent.clone();
        
System.out.println("temp1 == temp2 : " + (temp1 == temp2));
    執行結果為:

temp1 == temp2 : false
    很明顯,結果為false,因為複製出來是一個新的物件,引用不同。我們再對他們的各個變數進行一一比較:

System.out.println("temp1Age == temp2Age : " + (temp1.getAge() == temp2.getAge()));
System.out.println("temp1Name == temp2Name : " + (temp1.getName() == temp2.getName()));
System.out.println("temp1Son == temp2Son : " + (temp1.getSon() == temp2.getSon()));
    執行結果為:

temp1Age == temp2Age : true
temp1Name == temp2Name : true
temp1Son == temp2Son : true
   很奇怪,他們的內部屬性卻全部是相同的,這是為什麼呢?

   因為Object提供的clone是淺複製,如果是基本型別,則複製其值,如果是引用內容,則複製其引用。所以兩者指向的地址是相同的,故相等。

    2.4  getClass

public final native Class<?> getClass();
    此方法返回的是執行時類物件,這點從註釋可明顯看出:

Returns the runtime class of this {@code Object}.
    什麼是類物件?我們知道,在Java中,一切皆物件。在Java中,類是是對具有一組相同特徵或行為的例項的抽象並進行描述,物件則是此類所描述的特徵或行為的具體例項。作為概念層次的類,其本身也具有某些共同的特性,如都具有類名稱、由類載入器去載入,都具有包,具有父類,屬性和方法等。於是,Java中有專門定義了一個類,Class,去描述其他類所具有的這些特性。

  2.5  hashCode

public native int hashCode();
    這也是由non-java實現的方法,返回的是一個物件的雜湊值,型別為int。一般情況下,在當前程式的執行期間,一個物件多次呼叫hashCode方法,其返回的雜湊值是相同的。這裡需要注意的是:

    若兩個物件相等,則它們的雜湊值一定相等,反之,雜湊值相等,兩個物件不一定相等;

    若兩個物件不相等,則它們的雜湊值不一定相等,反之,雜湊值不同,兩個物件一定不相等。

    在Java中,有許多地方都應用到了hash,如集合set、map,equals等,這裡不做過多研究。

2.6  equals

public boolean equals(Object obj) {
     return (this == obj);
}
    關於“==”與equals的區別,面試的時候多數人都被問到過。我們知道“==”比較基本資料型別時,比較的是值,當比較物件時,比較的是其引用;而equals比較的是兩個物件是否相等。

    在Object類中,equals與“==”其實是等價的,但是我們在很多情況下是需要重寫equals方法的,例如比較兩個學生是否為同一個人,我們直接使用equals方法肯定是不可行的。通常情況下,我們認為如果學號相同,那麼這兩個學生就是同一個人。重寫的示例如下:

    Student類

/**
 * 用於測試重寫Object類equals方法
 * 
 * @author xuyong
 *
 */
public class Student {
 
    private String no;        //學號
    
    private String name;    //姓名
 
    public Student(String no, String name) {
        super();
        this.no = no;
        this.name = name;
    }
 
    public Student() {
        super();
    }
 
    public String getNo() {
        return no;
    }
 
    public void setNo(String no) {
        this.no = no;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}
    測試類

Student stu1 = new Student("1", "路人甲");
Student stu2 = new Student("1", "路人乙");
System.out.println(stu1.equals(stu2));
    執行,控制檯列印false。此時,我們重寫一下Student類的equals方法:

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Student) {
            Student stu = (Student)obj;
            return no.equals(stu.getNo());
        }
        return super.equals(obj);    
    }
    再次執行剛剛的程式碼,控制檯列印true。於是,我們便實現了通過學號判斷是否為同一個人的業務。

    不過,騷年們,以為這就結束了嗎?NO!

    由於Java需要維護hash的一般規律,沒錯就是剛剛標紅的內容:兩個物件相等,那麼他們的雜湊值必須相等。

    但是此時,我們測試一下他們的hash是否相等,可以明顯發現,他們是不相等的。

System.out.println(stu1.hashCode() == stu2.hashCode());
    所以,我們在重寫equals方法時,必須要重寫hashCode方法,由於我們是通過學號判斷的,所以最好也是使用學號的雜湊值替代原有的雜湊值:

    @Override
    public int hashCode() {
        return no.hashCode();
    }
    此時再執行方法,就會發現返回的是true了。

2.7  finalize

protected void finalize() throws Throwable { }
該方法用於垃圾回收,一般由 JVM 自動呼叫,一般不需要程式設計師去手動呼叫該方法。
————————————————
版權宣告:本文為CSDN博主「t1heluosh1」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/PostersXu/art