jdk源碼分析(一)——Object類

一.概述

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/

3. http://www.cnblogs.com/zuoxiaolong/p/pattern24.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,141評論 0 62
  • 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法,而不是構(gòu)造函數(shù)創(chuàng)建對象:僅僅是創(chuàng)建對象的方法,并非Fa...
    孫小磊閱讀 2,021評論 0 3
  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,366評論 11 349
  • 加油!用心去畫,不論美丑
    涼亭_3087閱讀 203評論 0 0