Object類--源碼分析

Object類沒有定義屬性,一共有13個方法,是所有類的祖先 ,位於java.lang 包裏。

 private static native void registerNatives();
    static {
        registerNatives();
    }

registerNatives函數前面有native關鍵字修飾,Java中,用native關鍵字修飾的函數表明該方法的實現並不是在Java中去完成,而是由C/C++去完成,並被編譯成了.dll,由Java去調用。方法的具體實現體在dll文件中,對於不同平臺,其具體實現應該有所不同。用native修飾,即表示操作系統,需要提供此方法,Java本身需要使用。具體到registerNatives()方法本身,其主要作用是將C/C++中的方法映射到Java中的native方法,實現方法命名的解耦。registerNatives()修飾符爲private,在Java源碼中,此方法的聲明後有緊接着一段靜態代碼塊去執行。

public final native Class<?> getClass();

認識 native 即 JNI,Java Native Interface

凡是一種語言,都希望是純。比如解決某一個方案都喜歡就單單這個語言來寫即可。Java平臺有個用戶和本地C代碼進行互操作的API,稱爲Java Native Interface (Java本地接口)。使用native關鍵字說明這個方法是原生函數,也就是這個方法是用C/C++語言實現的,並且被編譯成了DLL,由java去調用。這些函數的實現體在DLL中,JDK的源代碼中並不包含,你應該是看不到的。對於不同的平臺它們也是不同的。這也是java的底層機制,實際上java就是在不同的平臺上調用不同的native方法實現對操作系統的訪問的。

getClass()也是一個native方法,返回的是此Object對象的類對象/運行時類對象Class<?>。效果與Object.class相同。

 public boolean equals(Object obj) {
        return (this == obj);
    }

==與equals在Java中經常被使用,大家也都知道==與equals的區別:

==表示的是變量值完成相同(對於基礎類型,地址中存儲的是值,引用類型則存儲指向實際對象的地址);

equals表示的是對象的內容完全相同,此處的內容多指對象的特徵/屬性。

實際上,上面說法是不嚴謹的,更多的只是常見於String類中。首先看一下Object類中關於equals()方法的定義:【上圖】

由此可見,Object原生的equals()方法內部調用的正是==,與==具有相同的含義。既然如此,爲什麼還要定義此equals()方法?

equlas()方法的正確理解應該是:判斷兩個對象是否相等。那麼判斷對象相等的標尺又是什麼?

如上,在object類中,此標尺即爲==。當然,這個標尺不是固定的,其他類中可以按照實際的需要對此標尺含義進行重定義。如String類中則是依據字符串內容是否相等來重定義了此標尺含義。如此可以增加類的功能型和實際編碼的靈活性。當然了,如果自定義的類沒有重寫equals()方法來重新定義此標尺,那麼默認的將是其父類的equals(),直到object基類。

如下場景的實際業務需求,對於User bean,由實際的業務需求可知當屬性uid相同時,表示的是同一個User,即兩個User對象相等。則可以重寫equals以重定義User對象相等的標尺。【可以看看String 類的源碼,equal()方法第一句就是if(this == anObject)】

public native int hashCode()

hashCode()方法返回一個整形數值,表示該對象的哈希碼值。

hashCode()具有如下約定:

1).在Java應用程序程序執行期間,對於同一對象多次調用hashCode()方法時,其返回的哈希碼是相同的,前提是將對象進行equals比較時所用的標尺信息未做修改。在Java應用程序的一次執行到另外一次執行,同一對象的hashCode()返回的哈希碼無須保持一致;

2).如果兩個對象相等(依據:調用equals()方法),那麼這兩個對象調用hashCode()返回的哈希碼也必須相等;

3).反之,兩個對象調用hasCode()返回的哈希碼相等,這兩個對象不一定相等。

即嚴格的數學邏輯表示爲: 兩個對象相等 <=>  equals()相等  => hashCode()相等。因此,重寫equlas()方法必須重寫hashCode()方法,以保證此邏輯嚴格成立,同時可以推理出:hasCode()不相等 => equals()不相等 <=> 兩個對象不相等。

可能有人在此產生疑問:既然比較兩個對象是否相等的唯一條件(也是衝要條件)是equals,那麼爲什麼還要弄出一個hashCode(),並且進行如此約定,弄得這麼麻煩?

其實,這主要體現在hashCode()方法的作用上,其主要用於增強哈希表的性能。

以集合類中,以Set爲例,當新加一個對象時,需要判斷現有集合中是否已經存在與此對象相等的對象,如果沒有hashCode()方法,需要將Set進行一次遍歷,並逐一用equals()方法判斷兩個對象是否相等,此種算法時間複雜度爲o(n)。通過藉助於hasCode方法,先計算出即將新加入對象的哈希碼,然後根據哈希算法計算出此對象的位置,直接判斷此位置上是否已有對象即可。(注:Set的底層用的是Map的原理實現)

在此需要糾正一個理解上的誤區:對象的hashCode()返回的不是對象所在的物理內存地址。甚至也不一定是對象的邏輯地址,hashCode()相同的兩個對象,不一定相等,換言之,不相等的兩個對象,hashCode()返回的哈希碼可能相同。

因此,在上述代碼中,重寫了equals()方法後,需要重寫hashCode()方法。

protected native Object clone() throws CloneNotSupportedException

clone()方法又是一個被聲明爲native的方法,因此,我們知道了clone()方法並不是Java的原生方法,具體的實現是有C/C++完成的。clone英文翻譯爲"克隆",其目的是創建並返回此對象的一個副本。形象點理解,這有一輛科魯茲,你看着不錯,想要個一模一樣的。你調用此方法即可像變魔術一樣變出一輛一模一樣的科魯茲出來。配置一樣,長相一樣。但從此刻起,原來的那輛科魯茲如果進行了新的裝飾,與你克隆出來的這輛科魯茲沒有任何關係了。你克隆出來的對象變不變完全在於你對克隆出來的科魯茲有沒有進行過什麼操作了。Java術語表述爲:clone函數返回的是一個引用,指向的是新的clone出來的對象,此對象與原對象分別佔用不同的堆空間。

public String toString() {  
    return getClass().getName() + "@" + Integer.toHexString(hashCode());  
}  

toString()方法相信大家都經常用到,即使沒有顯式調用,但當我們使用System.out.println(obj)時,其內部也是通過toString()來實現的。getClass()返回對象的類對象,getClassName()以String形式返回類對象的名稱(含包名)。Integer.toHexString(hashCode())則是以對象的哈希碼爲實參,以16進制無符號整數形式返回此哈希碼的字符串表示形式。如上例中的u1的哈希碼是638,則對應的16進製爲27e,調用toString()方法返回的結果爲:com.corn.objectsummary.User@27e。

因此:toString()是由對象的類型和其哈希碼唯一確定,同一類型但不相等的兩個對象分別調用toString()方法返回的結果可能相同。

wait(...) / notify() / notifyAll()

一說到wait(...) / notify() | notifyAll()幾個方法,首先想到的是線程。確實,這幾個方法主要用於java多線程之間的協作。先具體看下這幾個方法的主要含義:

wait():調用此方法所在的當前線程等待,直到在其他線程上調用此方法的主調(某一對象)的notify()/notifyAll()方法。

wait(long timeout)/wait(long timeout, int nanos):調用此方法所在的當前線程等待,直到在其他線程上調用此方法的主調(某一對象)的notisfy()/notisfyAll()方法,或超過指定的超時時間量。

notify()/notifyAll():喚醒在此對象監視器上等待的單個線程/所有線程。

wait(...)方法調用後當前線程將立即阻塞,且適當其所持有的同步代碼塊中的鎖,直到被喚醒或超時或打斷後且重新獲取到鎖後才能繼續執行;notify()/notifyAll()方法調用後,其所在線程不會立即釋放所持有的鎖,直到其所在同步代碼塊中的代碼執行完畢,此時釋放鎖,因此,如果其同步代碼塊後還有代碼,其執行則依賴於JVM的線程調度。

protected void finalize() throws Throwable { }  

Object中定義finalize方法表明Java中每一個對象都將具有finalize這種行爲,其具體調用時機在:JVM準備對此對形象所佔用的內存空間進行垃圾回收前,將被調用。由此可以看出,此方法並不是由我們主動去調用的(雖然可以主動去調用,此時與其他自定義方法無異)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章