您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關Java之object類的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
Java基類Object
java.lang.Object,Java所有類的父類,在你編寫一個類的時候,若無指定父類(沒有顯式extends一個父類)編譯器(一般編譯器完成該步驟)會默認的添加Object為該類的父類(可以將該類反編譯看其字節碼,不過貌似Java7自帶的反編譯javap現在看不到了)。
再說的詳細點:假如類A,沒有顯式繼承其他類,編譯器會默認添加Object為其父類;若有,那么那個顯式父類呢?要么是沒有顯式繼承,那么Object是這個父類的父類,那肯定也是類A的父類,如果有,以此類推,所以,Object是Java所有類的祖先類(父類)。
聲明
1.本系列是JDK1.7(oracle)的源碼分析,若和你查看到的源碼有差異,請對比JDK版本。
2.本系列是本人對Java源碼的解析,但由于本人水平有限,勢必不能做到完全解讀,甚至只能說通過搜索閱讀學習,做一些表面的解析,有不足之處,望指教,望諒解。
Object源碼
public class Object { //本地方法,C/C++在DLL中實現,通過JNI調用 private static native void registerNatives(); //類初始化調用此方法 static { registerNatives(); } //返回此Object的運行時類(每個類的Class類對象) public final native Class<?> getClass(); //獲得該對象的hash值 public native int hashCode(); //對比兩對象的內存地址,如果不重寫,equals方法比較的是對象地址 public boolean equals(Object obj) { return (this == obj); } //本地clone方法,用于對象的賦值 protected native Object clone() throws CloneNotSupportedException; //返回對象的的字符串表示,默認是:類名+@+hash值 public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); //notify()/notifyAll()/wait()以及wait兩個重載方法都是線程同步相關方法 public final native void notify(); public final native void notifyAll(); public final native void wait(long timeout) throws InterruptedException; 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); } public final void wait() throws InterruptedException { wait(0); } //對象被回收時調用,不管如何,一個對象只調用一次 protected void finalize() throws Throwable { }
概述
因為Object是Java所有類的祖先類,所以Java所有類都有Object中的方法,在看這些方法的時候要聯系這些方法不是針對Objec一個類,而是所有類。
既然是所有類共有,設計的時候肯定想的是所有類的共性,比如:equals方法就是用來比較任意兩個相同類型對象是否相等的,toString是用來將任意對象轉換成String,方便打印查看。當然,以上方法的實現都是默認的,想要實現自己的邏輯需要在自己類中覆蓋重寫。
以上的native方法,在Oracle的jdk是看不到的,但在OpenJDK或其他開源JDK是可以找到對應的C/C++代碼的。
源碼詳解
1.構造方法
源碼中并沒有Object的構造方法,但是,同樣的,編譯器在編譯期間會給Object(事實上,所有的Java類,只要類中沒有構造方法,編譯器都會默認的給一個空構造方法,若已有構造方法,則不會添加)一個默認的空的構造方法:
public Object(){}
2.registerNatives
帶有native修飾的都是本地方法,所謂的本地方法是不通過Java語言實現的方法,但可以通過JNI,像調用Java方法一樣調用這些方法。詳細的可以搜索查看JNI。
這個方法的作用是對Object以下幾個本地方法(hashCode/clone/notify等)進行注冊(可以理解為,這個方法是告訴JVM這幾個本地方法的實現映射),每一個有本地方法的都會有這個方法,但其內容不一樣(因為注冊的方法不一樣嘛)。
3.getClass
每一個類在被加載的時候,都會生成一個Class類實例,而這個方法就可以在運行時期獲得對象(這里的對象是堆里的那個對象,也就是獲得的是動態類型的那個類)的Class對象,Class對象主要用于反射。
class A{} class B extends A{} class C extends B{} A a = new C();//對象new C()的靜態類型是A,動態類型是C B b = (B)a;//引用b指向的還是new C(),動態類型還是C C c = (C)b; System.out.println(a.getClass().getName()); System.out.println(b.getClass().getName()); System.out.println(c.getClass().getName()); //打印結果均是:com.xxx.test.C //對象的動態類型是不會變的,即new后面那個類型(構造對象的那個類型),但是靜態類 //型是由指向它的引用決定的,事實上可以這樣理解對象只有動態類型,引用類型才是靜態類型 //以上說的對象指的是堆里對象,而不是泛指Object o = new Object()中的o //不明白靜態類型,動態類型的可以自行百度
4.hashCode
獲得該對象的hash值,Java虛擬機規范并沒有規定這個方法的具體實現,只是規定了同一個對象兩次調用(任何條件情形下)這個方法返回的int值要想等(但并沒有規定兩個不同對象hash值一定不相同),具體實現由各個JVM廠商自己實現,所以返回的值意義并不一定(這里特指Object的hashCode方法),有可能返回的是對象的內存地址,也有可能是某個特定計算公式計算出來的值。
5.equals
原則上或則說語義上,設計目的上,equals的作用意義,是用來比較兩個對象是否相等,這里是我們通常理解的相等:即兩個對象其內容是否相等,而不是程序上來看,兩個對象是否是同一個對象,即比較其內存地址;如果想比較兩個對象是否是同一個對象(這里是說兩個引用是否指向同一個對象),直接用==比較即可(==比較的就是對象的內存地址)。但這里重要的是,對于Object來說,它并不能知道子類是如何判斷他們的兩個實例是如何equals的,所以,默認的equals實現,比較的是兩對象內存地址,即,若子類不重寫equals方法,其作用等同于==。
//如何重寫equals方法實現判斷內容相等? //關鍵點取決于你的邏輯,你想讓兩個對象在什么時候相等,你邏輯上就怎么寫 class A { public int a; public String b; public D d; @Override public boolean equals(Object o) { if (this == o) return true;//如果指向同一個對象,當然equals //如果o為null或兩個對象類型都不相同,當然不equals if (o == null || getClass() != o.getClass()) return false; //動態類型相同,強制轉換 A a1 = (A) o; /*下面是自己的邏輯,判斷兩個對象是否相同,邏輯1 Begin*/ if (a != a1.a) return false; if (b != null ? !b.equals(a1.b) : a1.b != null) return false; return d != null ? d.equals(a1.d) : a1.d == null; //全部字段相同,則equals。如果對象越復雜,想要實現全部字段相同,也就越復雜 /* 邏輯1 End */ /* 邏輯2 begin*/ //只要字段a相同,就認為兩個對象equals if(a == a1.a) return true; /* 邏輯2 end*/ } @Override public int hashCode() { int result = a; result = 31 * result + (b != null ? b.hashCode() : 0); result = 31 * result + (d != null ? d.hashCode() : 0); return result; } } class D{ public int a; }
網上說的,重寫equals方法,必重寫hashCode,其實不然,若確定所有地方都沒有用到類似Map的地方,就不必重寫hashCode,因為Map的諸多方法是有用到hashCode方法判斷兩對象是否相等,而若你僅僅是自己用來判斷兩個對象是否equals,也就不必重寫hashCode(當然,還要確定其他地方不會用到hashCode的地方,比如,以后用,別人用等,不過一般的,推薦重寫hashCode方法,這樣保證任何地方都不會因此出錯)。
若hash值不相等,則兩個對象肯定不等(不equals);
若hash值相等,兩個對象不一定相等(不一定equals)。
equals相等,hash值肯定想等,也就是說,hash值相等時equals相等的必要條件。
hashCode方法一般用來判斷兩個對象equals前置條件,用來排除,這樣做的原因是,hashCode方法速度快,不相等的可快速否決掉,若hash相同,則再調用equals判斷。
6.clone
克隆對象,克隆一個與原先對象所有字段值相等的對象,從而獲得一個新的對象,需要注意的是:
想要使用這個方法,對象類型必須實現Cloneable接口,否則會報錯,原因是Object的clone方法有對對象類型驗證,如沒實現則報錯拋異常;
clone方法返回的是一個新的對象,這個對象的創建不是通過new(除非你像下面那樣不通過Object的clone方法重寫)指令,而是JVM通過其他指令創建的;
clone有深度clone和淺clone,這主要是針對類中間具有引用類型而言劃分的,詳情可參看:Java clone深度解析。
class A{} A a = new A(); a.clone();//報錯,即拋CloneNotSupportedException異常 class A implements Cloneable{}//這樣才不會 //但,若你重寫clone方法,并且在這個方法中沒有調用父clone(也就是Object)方法 class A{ @Override public Object clone() throws CloneNotSupportedException{ return new A(); } } a.clone();//這個時候調用clone方法即使沒有實現Cloneable方法也不會報錯 //說白了,你要理解為什么調用clone方法要實現Cloneable的原因,而不是僅僅是記住 //當你理解了,你就能熟練掌握這些規則,而不是記住他們
7.toString
toString這個方法算是Object比較常用的方法了,它的意義是提供將類的字段以String形式格式化輸出這一功能,當然,同樣的,Object不可能知道子類的字段信息,所以,默認toString輸出的是:全路徑類名+@+hash值。
若你想要輸出類的字段信息,需要重寫toString方法,將該類字段信息以你自己的格式輸出。
8.notify/notifyAll/wait
這三個方法適用于線程同步,這里只簡單介紹其作用,詳細請參考:notify/notifyAll/wait。
notify:隨機喚醒等待(wait)隊列中一個對象,使其需要該對象的線程繼續執行;
notifyAll:喚醒隊列中所有對象
wait:該對象陷入等待狀態,需要該對象的線程將不能再繼續執行,直到該對象由其他線程調用notify/notifyAll方法喚醒。
9.finalize
在對象被GC(垃圾回收,詳情可參考:Java GC概述)之前被調用(JVM主動調用),你可以重寫這個方法,然后在這個對象回收之前做某些動作,這個方法對于這個對象來說只能調用一次,為什么會這么說呢?對象都回收了,沒了,難道不是當然只能調用一次?不是這樣的,若你理解了Java GC原理便知道,若當你在finalize方法中,將這個對象重新賦予了強引用,GC這個對象將失敗,這個對象將繼續存活,而下次這個對象又成為可回收對象了,GC回收這個對象的時候,這個對象的finalize方法將不會再執行。
另外,需要區分的是:
finalize不是C/C++中的析構函數,更不是釋放內存的方法,它只是提供了在回收一個對象之前做某些操作,如果你熟悉C ,那你知道C 允許你為一個類定義一個撤消函數(destructor ),它在對象正好出作用域之前被調用。Java不支持這個想法也不提供撤消函數。finalize() 方法只和撤消函數的功能接近。當你對Java 有豐富經驗時,你將看到因為Java使用垃圾回收子系統,幾乎沒有必要使用撤消函數。
而且,在設計之初,這個方法就是為了兼容C/C++程序員習慣(對的,貌似就是這樣),后來設計者也說,這是個失敗的設計,所以,可以的話,在實踐中忘掉這個方法吧。
class D{ public static D d111; @Override protected void finalize() throws Throwable { super.finalize(); d111 = this;//這個時候該對象第一次回收將失敗,而以后將不會在執行該方法 System.out.println("finalize a = " + this.a); } } D d = new D(); d = null; //程序結束 //這個時候,雖然程序結束了,new D()對象也是可回收對象了,但是并不會執行 //finzlize,因為對于JVM來說GC的觸發條件是內存不足,所以不會執行GC也就不會調用 //finzlize方法
感謝各位的閱讀!關于“Java之object類的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。