Java中Object用法詳解
一. Object簡介
1. 簡介
在了解Object中的常用方法之前,我們先來看看Object類的源碼,如下所示:
/** * Class {@code Object} is the root of the class hierarchy. * Every class has {@code Object} as a superclass. All objects, * including arrays, implement the methods of this class. * @author unascribed * @see java.lang.Class * @since JDK1.0 */ public class Object { ......
從Object類的源碼注釋可以知道,Object類是Java中所有類的父類,相當于是Java中的”萬類之王“,處于最頂層。 所以在Java中,所有的類默認都繼承自Object類。同時Java中的所有類對象,包括數(shù)組,也都要實現(xiàn)這個類中的方法。
所以,Object是Java中所有類的父類、超類、基類,位于繼承樹的最頂層??梢哉f,任何一個沒有顯式地繼承別的父類的類,都會直接繼承Object,否則就是間接地繼承Object,并且任何一個類也都會享有Object提供的方法。又因為Object是所有類的父類,所以基于多態(tài)的特性,該類可以用來代表任何一個類,允許把任何類型的對象賦給 Object類型的變量,也可以作為方法的參數(shù)、方法的返回值。
二. 常用方法
在Object類中,自帶了幾個常用的方法,這幾個方法任意的子類都會繼承,如下圖所示:
根據(jù)上圖,小編把Object類中的常用方法歸納為這么幾種:
構造方法;
hashCode()和equals()方法用來判斷對象是否相同;
wait()、wait(long)、wait(long,int)、notify()、notifyAll();
toString()和getClass();
clone();
finalize()
接下來小編就給各位介紹Object類中的幾個常用方法,分別說一下這些方法的功能作用。
1. clone()方法
1.1 clone方法作用
Object中有兩個protected修飾的方法,其中一個就是clone()方法,并且該方法還是一個native方法。clone()方法用于創(chuàng)建復制出當前類對象的一個副本,得到一個復制對象。 所謂的復制對象,首先會分配一個和源對象(調用clone方法的對象)同樣大小的內存空間,在這個內存空間中會創(chuàng)建出一個新對象;然后再使用源對象中對應的各個成員,填充新對象的成員,填充完成之后,clone方法會創(chuàng)建返回一個新的相同對象供外部引用。
1.2 clone源碼分析
我們再看看clone()方法源碼上的注釋,如下圖所示:
從這段注釋中,我們可以了解到:
以x為藍本創(chuàng)建出的副本,與x對象并不相同,這保證了克隆出的對象擁有單獨的內存空間;
源對象和克隆的新對象字節(jié)碼相同,它們具有相同的類類型,但這并不是強制性的;
源對象和克隆的新對象利用equals()方法比較時是相同的,但這也不是強制性的。
1.3 Java的淺克隆與深克隆
因為每個類的直接或間接父類都是Object,因此它們都含有clone()方法,但因該方法是protected修飾的,所以我們不能在類外訪問該方法。但如果我們要對一個對象進行復制,可以對clone方法進行復寫,而Java中提供了兩種不同的克隆方式,淺克隆(ShallowClone) 和深克隆(DeepClone)。
1.3.1 淺克隆
在淺克隆中,如果源對象的成員變量是值類型,則復制一份給克隆對象;如果源對象的成員變量是引用類型,則將引用對象的地址復制一份給克隆對象,也就是說源對象和克隆對象的成員變量指向相同的內存地址。
簡單說,在淺克隆中,當對象被復制時只復制它本身和其中包含的值類型的成員變量,而引用類型的成員對象并沒有復制。我們可以用下圖對淺克隆進行展示:
在Java語言中,通過實現(xiàn)Cloneable接口,默認覆蓋Object類的clone()方法就可以實現(xiàn)淺克隆。
1.3.2 深克隆
在深克隆中,無論源對象的成員變量是值類型還是引用類型,都將復制一份給克隆對象,即深克隆將源對象的所有引用對象也復制一份給克隆對象。
簡單來說,在深克隆中,除了對象本身被復制外,對象中包含的所有成員變量也將復制。我們可以用下圖對深克隆進行展示:
在Java語言中,如果需要實現(xiàn)深克隆,可以通過實現(xiàn)Cloneable接口,自定義覆蓋Object類的clone()方法實現(xiàn),也可以通過序列化(Serialization)等方式來實現(xiàn)。 如果引用類型里面還包含很多引用類型,或者內層引用類型的類里面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現(xiàn)對象的深克隆。
2. hashCode()方法
2.1 簡介
hashCode()是Object中的一個native方法,也是所有類都擁有的一個方法,主要是返回每個對象十進制的hash值。hash值是由hash算法根據(jù)對象的地址、對象中的字符串、數(shù)字等計算出來的。一般情況下,相同的對象應會返回相同的哈希嗎值,不同的對象會返回不同的哈希碼值。
2.2 hash值
哈希值是根據(jù)地址值換算出來的一個值,并不是實際的地址值,常用于哈希表中,如HashMap、HashTable、HashSet。關于哈希值,不同的JDK算法其實是不一樣的:
- Java 6、7 中會默認返回一個隨機數(shù);
- Java 8 中默認通過和當前線程有關的一個隨機數(shù) + 三個確定值,運用Marsaglia’s xorshift scheme的隨機數(shù)算法得到的一個隨機數(shù)。
2.3 案例
Dog dog01=new Dog("喬治01"); Dog dog02=new Dog("喬治02"); //兩個對象的hash值是不同的 System.out.println("dog01的hash值 "+dog01.hashCode()); System.out.println("dog02的hash值 "+dog02.hashCode());
以上兩個對象的hash值是不同的,表示這是不同的兩個對象。
3. equals(obj)方法
3.1 equals簡介
Object中的equals方法用于判斷this對象和obj本身的值是否相等,即用來判斷調用equals方法的對象和形參obj所引用的對象是否是同一對象。 所謂同一對象,就是指兩個對象是否指向了內存中的同一塊存儲單元地址。如果this和obj指向的是同一塊內存單元地址,則返回true;如果this和obj指向的不是同一塊內存單元地址,則返回false。如果沒有指向同一內存單元,即便是內容完全相等,也會返回false。
Object類的equals方法,其作用是比較兩個對象是否相同,默認比較的是內存地址,其底層是通過==實現(xiàn)的。如果我們不想比較內存地址,那么就需要重寫equals方法。默認的實現(xiàn)源碼如下:
public boolean equals(Object obj) { return (this == obj); }
我們知道,Java中還有一個==運算符,也可以對兩個對象進行比較。如果是基本數(shù)據(jù)類型,==比較的是它們的值是否相同;如果是引用數(shù)據(jù)類型,比較的是它們的內存地址是否相同。而equals()方法則是比較兩個對象的內容是否相等。
3.2 使用原則
我們在使用equals()方法時,需注意下面這些原則:
(1).equals()只能處理引用類型變量;
(2).一般情況下,equals()方法比較的是兩個引用類型變量的地址值是否相等;
(3).但是String類、基本類型包裝類、File類、Date類等,都重寫了Object類的equals()方法,比較是兩個對象的"具體內容"是否相同。
3.3 基本特性
另外Java語言規(guī)范也要求equals方法具有下面的特性:
自反性 : 對于任何非空引用x,x.equals(x)應該返回true;
對稱性:對于任何引用x和y,當且僅當y.equals(x)返回true,x.equals(y)也應該返回true;
傳遞性:對于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也應該返回true;
一致性 : 如果x和y引用的對象沒有發(fā)生變化,反復調用x.equals(y)應該返回同樣的結果;
對于任何非空引用x,x.equals(null)應該返回false。
3.4 案例
/** * @author 一一哥Sun * 千鋒教育 */ public class ObjectTest { public static void main(String[] args) { Dog dog01=new Dog("喬治01"); Dog dog02=new Dog("喬治02"); System.out.println("dog01對比dog02 "+(dog01==dog02));//false //equals()方法的底層默認還是利用==實現(xiàn)的 System.out.println("dog01對比dog02 "+(dog01.equals(dog02)));//false } }
從上面的案例中,我們也可以證明,equals()方法用于處理引用類型的變量,默認比較的是兩個引用類型的變量地址是否相等。
4. getClass()方法
4.1 簡介
getClass()方法可以用于獲取對象運行時的字節(jié)碼類型,得到該對象的運行時的真實類型。該方法屬于Java的反射機制,其返回值是Class類型,例如 Class c = obj.getClass();。
通過對象c,我們可以進一步獲取該對象的所有成員方法,每個成員方法都是一個Method對象。我們也可以獲取該對象的所有成員變量,每個成員變量都是一個Field對象。同樣的,我們也可以獲取該對象的構造函數(shù),構造函數(shù)則是一個Constructor對象。
4.2 案例
/** * @author 一一哥Sun * 千鋒教育 */ public class ObjectTest { public static void main(String[] args) { //判斷運行時d對象和c對象是否是同一個類型 Animal d = new Dog(); Animal c = new Cat(); //方式1:通過instanceof關鍵字判斷 if((d instanceof Dog && c instanceof Dog) ||(d instanceof Cat && c instanceof Cat)) { System.out.println("是同一個類型"); }else { System.out.println("不是同一個類型"); } //方式2:通過getClass方法判斷 if(d.getClass() == c.getClass()) { System.out.println("是同一個類型"); }else { System.out.println("不是同一個類型"); } } }
從上面的代碼案例中,我們可以得知,getClass方法用于返回該對象的真實類型(運行時的類型),可以根據(jù)對象的字節(jié)碼來判斷兩個對象是否是同一個對象。
5. toString()方法
5.1 簡介
toString()方法可以說是一個進行“自我描述”的方法,可以返回某個對象的字符串,當要輸出某個實例對象的信息時,我們可以通過重寫該方法來輸出自我描述的信息。該方法通常只是為了方便輸出本類的描述信息,比如執(zhí)行System.out.println("xyz")
這樣的日志語句。一般情況下,當程序要輸出一個對象或者把某個對象和字符串進行連接運算時,系統(tǒng)就會自動調用該對象的toString()方法返回該對象的字符串表示。
Object類的toString()方法會返回“運行時的類名@十六進制哈希碼”格式的字符串,但很多類都重寫了 Object類的toString()方法,用于返回可以表述該對象信息的字符串。
5.2 案例
/** * @author 一一哥Sun * 千鋒教育 */ public class Dog implements Animal{ private String name; public Dog() {} public Dog(String name) { this.name = name; } @Override public void eat() { System.out.println("小狗"+this.name+"狗愛吃骨頭"); } //@Override //public String toString() { //return "Dog name= " + name; //} } public class ObjectTest { public static void main(String[] args) { Dog dog=new Dog("喬治"); System.out.println("dog一號="+dog); System.out.println("dog二號="+dog.toString()); } }
上述代碼執(zhí)行結果如下圖所示:
從上面程序的運行結果可以發(fā)現(xiàn),默認情況下,對象帶不帶toString()方法,其最終的輸出結果是一樣的,即對象輸出時一定會調用 Object類中的 toString()方法打印內容,所以我們可以利用此特性來通過 toString()方法取得一些對象的信息。
6. wait()、wait(long)、wait(long,int)、notify()、notifyAll()方法
這幾個函數(shù)體現(xiàn)的是Java的多線程機制,一般是結合synchronize語句使用。
- wait()用于讓當前線程失去操作權限,當前線程進入等待序列;
- notify()用于隨機通知一個持有對象的鎖的線程獲取操作權限;
- wait(long) 和wait(long,int)用于設定下一次獲取鎖的距離當前釋放鎖的時間間隔;
- notifyAll()用于通知所有持有對象的鎖的線程獲取操作權限。
這幾個方法我們后面在分析多線程的面試題時再細說,此處先僅做了解。
7. finalize()方法
7.1 簡介
finalize()方法在進行垃圾回收的時候會用到,主要是在垃圾回收時,用于作為確認該對象是否確認被回收的一個標記。我們在使用finalize()方法時要注意:
- finalize方法不一定會執(zhí)行,只有在該方法被重寫的時候才會執(zhí)行;
- finalize方法只會被執(zhí)行一次;
- 對象可以在finalize方法中獲得自救,避免自己被垃圾回收,同樣的自救也只能進行一次;
- 不推薦Java程序員手動調用該方法,因為finalize方法代價很大。
7.2 案例
為了測試出finalize()方法的作用,小編給大家設計了如下案例:
/** * @author 一一哥Sun * 千鋒教育 */ public class Dog implements Animal{ private String name; public Dog() {} public Dog(String name) { this.name = name; } @Override public void eat() { System.out.println("小狗"+this.name+"狗愛吃骨頭"); } //復寫finalize方法 @Override protected void finalize() throws Throwable { super.finalize();//不要刪除這行代碼 System.out.println("finalize方法執(zhí)行了"); } }
然后我們對Dog對象進行回收測試:
public class ObjectTest { public static void main(String[] args) { Dog dog=new Dog("喬治"); //手動將對象標記為垃圾對象 dog = null; //觸發(fā)垃圾回收器,回收垃圾對象 System.gc(); } }
要想確保finalize()方法的執(zhí)行,我們首先需要在相關對象中重新finalize()方法,然后將待回收的對象手動標記為null,最后再手動調用gc()方法,這樣才有可能確保finalize()方法一定執(zhí)行。
三. 結語
至此,就把Object類給大家介紹完畢了,這個類的內容并不是很難,主要是掌握幾個常用的方法就可以了,尤其是equals()、hashCode()、toString()、getClass()等方法。
以上就是Java中Object用法詳解的詳細內容,更多關于Java Object的資料請關注腳本之家其它相關文章!
相關文章
輸出java進程的jstack信息示例分享 通過線程堆棧信息分析java線程
通過ps到java進程號將進程的jstack信息輸出。jstack信息是java進程的線程堆棧信息,通過該信息可以分析java的線程阻塞等問題。2014-01-01