一.概述
Object類是java中所有類的父類,所有類默認(rèn)(而非顯式)繼承Object。這也就意味著,Object類中的所有公有方法也將被任何類所繼承。如果,整個java類體系是一顆樹,那么Object類毫無疑問就是整棵樹的根,因此值得我們仔細(xì)研讀(以下代碼基于jdk1.6)。
Object類中的方法如下:
下面我們逐一介紹。
二.核心方法
1.equals方法
默認(rèn)的實現(xiàn)是:
可以看出默認(rèn)情況下equals進行對象比較時只判斷了對象是否是其自身,當(dāng)我們有特殊的“相等”邏輯時,則需要覆蓋equals方法。
equals方法的通用約定:
自反性:對于任何非null的引用值x,x.equals(x)必須返回true。
對稱性:對于任何非null的引用值x和y,當(dāng)且僅當(dāng)y.equals(x)返回true時,x.equals(y)必須返回true。
傳遞性:對于任何非null的引用值x、y、z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也必須返回ture。
一致性:對于任何非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調(diào)用x.equals(y)就會一致的返回ture,或者一致的返回false。
非空性:對于任何非null的引用值x,x.equals(null)必須返回false。
當(dāng)我們在類繼承尤其要注意實現(xiàn)的equals方法是否滿足約定。一個不滿足約定的例子:
我們有一個水果類:
還有一個蘋果類:
如果我們執(zhí)行一下比較:
則會得到如下結(jié)果:
很顯然,這并不符合對稱性。
事實上:我們無法在擴展可實例化的類的同時,既增加新的值組件,同時又保留equals的約定,除非愿意放棄面向?qū)ο蟮某橄笏鶐淼膬?yōu)勢。
那這個問題該如何解決呢:
方法一:
不要拿Friut和Apple去比,這依賴于我們的主觀自覺,雖然可以既利用繼承帶來的優(yōu)勢,但總歸還要取決于人,有一定風(fēng)險。
方法二:
將繼承轉(zhuǎn)為組合。
這時,再進行比較:
此時,結(jié)果是滿足equals約定的:
2.hashCode方法
當(dāng)我們覆蓋了equals方法時,一定不能忘記覆蓋hashCode方法。
hashCode方法的約定:
(1)在應(yīng)用程序執(zhí)行期間,只要對象的equals方法的比較操作所用到的信息沒有被修改,那么對這同一個對象調(diào)用多次,hashCode方法都必須始終如一的返回同一個整數(shù)。在同一個應(yīng)用程序的多次執(zhí)行過程中,每次執(zhí)行所返回的整數(shù)可以不一致。
(2)如果兩個對象根據(jù)equals(Object)方法比較是相等的,那么調(diào)用這兩個對象中任意一個對象的hashCode方法都必須產(chǎn)生同樣的整數(shù)結(jié)果。
(3)如果兩個對象根據(jù)equals(Object)方法比較是不相等的,那么調(diào)用這兩個對象中任意一個對象的hashCode方法,則不一定要產(chǎn)生不同的整數(shù)結(jié)果。(盡管如此,理想的hashCode方法對不相等的對象,應(yīng)當(dāng)提供不同的hashCode值,這樣當(dāng)我們將對象作為Map的key時,可以提高散列表的性能)
如果我們覆蓋了equals方法,但是沒有覆蓋hashCode方法,就會違反上述第2條約定。我們可以用代碼來驗證一下:
執(zhí)行結(jié)果如下:
這會帶來什么后果呢?如果我們把fruit1放到HashMap中,隨后試圖用一個“相等”的水果來找回時,就會出問題。如下:
執(zhí)行程序,得到如下結(jié)果:
如何覆蓋hashCode方法
一個好的散列函數(shù)通常傾向于“為不相等的對象產(chǎn)生不相等的散列碼”。理想情況下,散列函數(shù)應(yīng)該把集合中不相等的實例均勻的分布到所有可能的散列值上。
《effective java》一書中提到了一種簡單的解決辦法:
(1)把某個非零的常數(shù)值,比如17,保存在一個名為result的int類型的變量中。
(2)對于對象中的每個關(guān)鍵域f(equals方法中涉及的每個域,如果equals方法中沒有涉及,則一定要排除在外),完成如下步驟:
a.為該域計算int類型的散列碼c:
i.如果該域是boolean類型,則計算(f?1:0)。
ii.如果該域是byte、char、short或者int類型,則計算(int)f。
iii.如果該域是long類型,則計算(int)(f^(f>>>32))。
iv.如果該域是float類型,則計算Float.floatToIntBits(f)。
v.如果該域是double類型,則計算Double.doubleToLongBits(f),然后按照步驟iii中所述,為得到的long類型值計算散列值。
vi.如果該域是一個對象引用,并且該類的equals方法通過遞歸的調(diào)用equals的方式來比較這個域,則同樣為這個域遞歸的調(diào)用hashCode。如果這個域的值為null,則返回0。
vii.如果該域是一個數(shù)組,則要把每一個元素當(dāng)作單獨的域來處理,也就是說,遞歸地應(yīng)用上述規(guī)則,對每個重要元素計算一個散列碼,然后根據(jù)步驟2.b中的做法把這些散列值組合起來。也可以使用Arrays.hashCode方法。
b.按照下面的公式,把步驟(2)a中計算得到的散列碼c合并到result中:
result = 31 * result + c。
(3)返回result。
如果對我們之前提到的Fruit類重寫hashCode方法,代碼如下:
3.clone方法
通用約定:
clone方法將創(chuàng)建和返回該對象的一個拷貝。這個“拷貝”的精確含義取決于該對象的類。一般的含義是,對于任何對象x,表達式x.clone() != x 將會是true,并且,表達式x.clone().getClass() == x.getClass()將會是true。并且通常情況下,表達式x.clone().equals(x)將會是true。
一個標(biāo)準(zhǔn)的clone實現(xiàn)需要做到以下兩點:
(1)調(diào)用super.clone()方法
(2)對于對象中的所有引用類型,均需要實現(xiàn)Cloneable接口,并重寫clone方法,然后對每個引用執(zhí)行clone方法。
示例代碼:
使用clone方法的優(yōu)點:
(1)速度快。clone方法最終會調(diào)用Object.clone()方法,這是一個native方法,本質(zhì)是內(nèi)存塊復(fù)制,所以在速度上比使用new創(chuàng)建對象要快。
(2)靈活。可以在運行時動態(tài)的獲取對象的類型以及狀態(tài),從而創(chuàng)建一個對象。
當(dāng)然,使用clone方法創(chuàng)建對象的缺點同樣非常明顯:
(1)實現(xiàn)深拷貝較為困難,需要整個類繼承系列的所有類都很好的實現(xiàn)clone方法。
(2)需要處理CloneNotSupportedException異常。Object類中的clone方法被聲明為可能會拋出CloneNotSupportedException,因此在子類中,需要對這一異常進行處理。
因此,我們?nèi)绻胧褂胏lone方法的話,需要非常謹(jǐn)慎。事實上,《Effective Java》的作者Joshua Bloch建議我們不應(yīng)該實現(xiàn)Cloneable接口,而應(yīng)該使用拷貝構(gòu)造器或者拷貝工廠。
4.toString方法
Object類提供了默認(rèn)的toString方法實現(xiàn):
從代碼中我們可以看出,默認(rèn)的toString方法僅僅返回類名+當(dāng)前實例hashCode值的十六進制串,這非常不便于閱讀,且沒有包含實例中屬性的值。
通過重寫toString方法,我們可以輸出需要的實例信息,例如可以對實例屬性值進行格式化顯示等等。如下所示:
除了手工編寫格式化方式,我們還可以借助于一些第三方類庫來實現(xiàn)實例的格式化,例如使用Apache Commons Lang工具包中的ToStringBuilder類。
三.其他方法
1.registerNatives方法
該方法被聲明為是一個private static native方法,該方法的調(diào)用執(zhí)行是在接下來聲明的static塊中:
由于是native方法,因此源代碼我們不能直接看到,而在相關(guān)的C++代碼中。該方法主要是為了服務(wù)于JNI(Java Native Interface)的,它主要是提供了java類中的方法與對應(yīng)C++代碼中的方法的映射,方便jvm查找調(diào)用C++中的方法。
2.getClass方法
該方法被聲明為public final native方法,這說明該方法無法被重寫,且是一個本地方法,通過API文檔,我們可以了解到:該方法將返回對象的運行時類。例如:
運行結(jié)果如下:
獲得運行時類信息后,我們接下來就可以做更多的事情:
(1)生成類的實例
(2)獲取類中的方法、屬性
(3)獲取類的注解
(4)獲取類所在的包
……
有關(guān)Class類,我們后續(xù)還會專門學(xué)習(xí)。
3.wait、notify、notifyAll方法
這三個方法都是public final native的。不可以被子類重新,且都是本地方法。這三個方法提供了java線程間等待、掛起等協(xié)同機制,是java多線程的基礎(chǔ),也留待后續(xù)深入學(xué)習(xí)。
4.finalize方法
根據(jù)java api中的說法:
“當(dāng)垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調(diào)用此方法”。
“finalize方法可以采取任何操作,其中包括再次使此對象對其他線程可用;不過,finalize的主要目的是在不可撤消地丟棄對象之前執(zhí)行清除操作”。
“Java 編程語言不保證哪個線程將調(diào)用某個給定對象的finalize方法”。
“對于任何給定對象,Java 虛擬機最多只調(diào)用一次finalize方法”。
盡管finalize在某些時候是有用的,但是在大部分情況下,還是不建議使用,基于以下幾點:
(1)不保證會被jvm執(zhí)行,且不知道何時才會執(zhí)行。這就給程序執(zhí)行帶來了很大不確定性。
(2)不同的jvm垃圾回收算法不一致,在一個jvm上工作良好,可能在另一個jvm上未必有效。
(2)性能。根據(jù)Joshua Bloch在《Effective Java》中的描述,增加了finalize后,對象的創(chuàng)建和銷毀時間慢了430倍。
本文已遷移至我的博客:http://ipenge.com/21344.html
參考資料:
1.《effective java》
2. http://techbook.blog.163.com/blog/static/304885102012235613945/