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