Java Object類方法解析
Java Object類方法解析
在Java中Object是所有類的父類,任何類都默認繼承Object,其提供的方法主要有以下幾種:
-
registerNatives()
-
hashCode和equale函數用來判斷對象是否相同,
-
wait(),wait(long),wait(long,int),notify(),notifyAll()
-
toString()和getClass
-
clone()
-
finalize()用於在垃圾回收
下面介紹下各個方法:
registerNatives()
registerNatives函數前面有native關鍵字修飾,Java中,用native關鍵字修飾的函數表明該方法的實現並不是在Java中去完成,而是由C/C++ 去完成,並被編譯成了.dll,由Java去調用。方法的具體實現體在dll文件中,對於不同平台,其具體實現應該有所不同。用native修飾,即表示作業系統,需要提供此方法,Java本身需要使用。具體到registerNatives()方法本身,其主要作用是將C/C++中的方法映射到Java中的native方法,實現方法命名的解耦。
在Java源碼中,此方法在靜態程式碼塊中被調用:
private static native void registerNatives ();
static {
registerNatives();
}
equales
equales經常跟「= =」做比較,兩者區別是:
「= =」表示的是變數值完全相同(對於基礎類型,地址中存儲的是值,引用類型則存儲指向實際對象的地址,所以基本數據類型:「= =」比較的是他們的值。引用類型(類、介面、數組) :用 「= =」 進行比較的時候,比較的是他們在記憶體中的存放地址,除非是同一個new出來的對象,他們的比較後的結果為true,否則比較後結果為false。)
我們知道對象在JVM中是存放放在Java堆中的,Java虛擬機棧中存放的是對象的引用(地址)。由此可見 「= =」 是對Java虛擬機棧中的值進行比較的。如果要比較Java堆中對象的內容是否相同,那麼就需要使用equals方法了。
equals表示的是對象的內容完全相同,此處的內容多指對象的特徵/屬性。 Object類中關於equals()方法的定義如下:
public boolean equals(Object var1) {
return this == var1;
}
Object原生的equals()方法內部調用的正是= =,與= =具有相同的含義。既然如此,為什麼還要定義此equals()方法?
equlas()方法的正確理解應該是:判斷兩個對象是否相等。那麼判斷對象相等的標尺又是什麼?
如上,在object類中,此標尺即為==。當然,這個標尺不是固定的,其他類中可以按照實際的需要對此標尺含義進行重定義。如String類中則是依據字元串內容是否相等來重定義了此標尺含義。如此可以增加類的功能性和實際編碼的靈活性。當然了,如果自定義的類沒有重寫equals()方法來重新定義此標尺,那麼默認的將是其父類的equals(),直到object基類。
String 類的equals方法:
public boolean equals(Object var1) {
if (this == var1) {
return true;
} else {
if (var1 instanceof String) {
String var2 = (String)var1;
int var3 = this.value.length;
if (var3 == var2.value.length) {
char[] var4 = this.value;
char[] var5 = var2.value;
for(int var6 = 0; var3-- != 0; ++var6) {
if (var4[var6] != var5[var6]) {
return false;
}
}
return true;
}
}
return false;
}
}
String中equals方法判斷相等的步驟是:
1).若A==B 即是同一個String對象 返回true
2).若對比對象是String類型則繼續,否則返回false
3).判斷A、B長度是否一樣,不一樣的話返回false
4).逐個字元比較,若有不相等字元,返回false
註:Java中約定重寫equals()方法必須重寫hasCode()方法。
最後總結下:
==比較的是「值」(引用數據類型是比較其記憶體地址值,基本數據類型是比較變數值)
equals比較的是對象的內容,對內容比較的標尺可以由子類自己定義。
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()返回的哈希碼可能相同。
小結:
1).如果兩個對象equals,Java運行時環境會認為他們的hashcode一定相等。
2).如果兩個對象不equals,他們的hashcode有可能相等。
3).如果兩個對象hashcode相等,他們不一定equals。
4).如果兩個對象hashcode不相等,他們一定不equals。
為了比較兩個對象時更高效,判斷兩個對象是否相等可以採用以下規則:
第一步,如果hashCode()相等,則查看第二步,否則不相等;
第二步,查看equals()是否相等,如果相等,則兩obj相等,否則還是不相等。
wait(),wait(long),wait(long,int),notify(),notifyAll()
一說到wait(…) / notify() /notifyAll()這幾個方法,首先想到的是執行緒。確實,這幾個方法主要用於java執行緒之間的通訊協作。先具體看下這幾個方法的主要含義:
1、wait(…)方法調用後當前執行緒將立即阻塞,且釋放其所持有的同步程式碼塊中的鎖,直到被喚醒或超時或打斷後且重新獲取到鎖後才能繼續執行;
2、notify()/notifyAll()方法調用後,其所在執行緒不會立即釋放所持有的鎖,直到其所在同步程式碼塊中的程式碼執行完畢,此時釋放鎖,因此,如果其同步程式碼塊後還有程式碼,其執行則依賴於JVM的執行緒調度。
註:wait(…) / notify() / notifyAll()一般情況下都是配套使用。且wait(…)/notify()|notifyAll()方法只能在同步程式碼塊中才能使用。
為什麼Object這個基類會提供這類方法?因為定義在Object中就可以實現將任何對象視為執行緒同步中的監聽器。
對於wait和notify方法我們在Java執行緒部分還會再次分析。
toString()
toString()方法返回該對象的字元串表示。先看一下Object中的具體方法體:
public String toString() {
return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}
toString()方法相信大家都經常用到,即使沒有顯式調用,但當我們使用System.out.println(obj)時,其內部也是通過toString()來實現的。
Object類的toString()方法返回的是對象的類型和其哈希碼,子類可以重寫該方法以列印自己想要列印的內容。
getClass
getClass()是一個native方法,返回的是此Object對象的類對象/運行時類對象Class<?>。
首先解釋下」類對象」的概念:在Java中,類是是對具有一組相同特徵或行為的實例的抽象描述,對象則是此類所描述的特徵或行為的具體實例。作為概念層次的類,其本身也具有某些共同的特性,如都具有類名稱、由類載入器去載入,都具有包,具有父類,屬性和方法等。於是,Java中有專門定義了一個類,Class類,去描述其他類所具有的這些特性,從此角度去看,類本身是屬於Class類的對象,我們稱之為」類對象」。
clone()
clone()方法同樣是native方法,因此,我們知道了clone()方法並不是Java的原生方法,具體的實現是有C/C++完成的。clone英文翻譯為」克隆」,其目的是創建並返回此對象的一個副本。Java術語表述為:clone函數返回的是一個引用,該引用指向的是新的clone出來的對象,此對象與原對象分別佔用不同的堆空間。
同時Java中的語法規定:
clone()的正確調用是需要實現Cloneable介面,如果沒有實現Cloneable介面,並且子類直接調用Object類的clone()方法,會拋出CloneNotSupportedException異常。
Cloneable介面僅是一個表示介面,介面本身不包含任何方法,用來指示Object.clone()可以合法的被子類引用所調用。
finalize()
finalize方法主要與Java垃圾回收機制有關。首先我們看一下finalized方法在Object中的具體定義:
protected void finalize() throws Throwable {
}
我們發現Object類中finalize方法被定義成一個空方法,為什麼要如此定義呢?finalize方法的調用時機是怎麼樣的呢?
首先,Object中定義finalize方法表明Java中每一個對象都將具有finalize這種行為,其具體調用時機在:JVM準備對此對象所佔用的記憶體空間進行垃圾回收前,將會調用其finalize。由此可以看出,此方法並不是由我們主動去調用的。