概述
Object類是類層次結構的根類,可以作為各種的通用持有者。它是每個java類的基類,如果沒有明確指出基類,Object就被認為是當前定義的類的基類。包括arrays在內的所有物件,都實現Object類的方法。Object類屬於java.lang包,在這個類中有很多native(本地)方法。其類圖如下(JDK8):
構造方法
Object類沒有顯示的構造方法,只有編譯器預設提供的構造方法。
欄位
Object沒有欄位。
方法
Object只包含12個方法,但這些方法都十分重要。
按訪問限制級別可分為:
- public:
equals(Object)
,hashCode()
,notify()
,notifyAll()
,toString()
,wait()
,wait(long)
,wait(long, int)
- protected:
clone()
,finalize()
- private:
registerNatives()
按是否為final可分為:
- final:
getClass()
,notify()
,notifyAll()
,wait()
,wait(long)
,wait(long, int)
,registerNatives()
(類私有方法自動成為final)。這些方法不能被子類重寫。 - 非final:
hashCode()
,equals(Object)
,clone()
,toString()
,finalize()
。這些方法可以被子類重寫,且必須滿足通用的約定,否則其他依賴於這些約定的類就無法與該類正常工作。
按是否為native可分為:
- native:
registerNatives()
,getClass()
,hashCode()
,clone()
,notify()
,notifyAll()
,wait(long)
。這些方法是用C/C++在動態庫中實現的,然後通過JNI(java Native Inteface)呼叫。Java語言本身不能對作業系統底層進行訪問與操作,但可以通過JNI介面來呼叫其他語言來實現對底層的訪問,JNI已加入Java標準。 - 非native:
equals(Object)
,toString()
,wait()
,wait(long, int)
,finalize()
。
registerNatives()
方法
其主要作用是將C/C++中的方法對映到Java中的native方法,實現方法命名的解耦。在類初始化時呼叫static塊,執行registerNatives方法。
private static native void registerNatives();
static {
registerNatives();
}
複製程式碼
getClass()
方法
返回包含物件資訊的類物件(Class型別的例項),並且返回的類物件是被此類的靜態同步(static synchronized)方法鎖定的**(啥意思?)**。型別類Class表示一個型別的類,因為一切皆物件,型別也不例外,因此所有的型別類都是Class類的例項。
public final native Class<?> getClass();
複製程式碼
與執行緒有關的方法
包括wait
,notify
和notifyAll
,暫不分析,先佔個坑,之後補上。
finalize()
方法
類似C++的解構函式,當GC(Garbage Collector)確定不存在對該物件的更多引用時,由物件的GC呼叫此方法,用於釋放資源。一般不建議使用此方法來釋放非記憶體資源。它不同於C++的解構函式,解構函式在物件作用域呼叫,執行時間點確定。而此方法,是在記憶體不足,GC發生時進行呼叫,由於GC是不確定隨機的,所以無法確定此方法的執行時間。
protected void finalize() throws Throwable {}
複製程式碼
equals(Object)
方法
equals方法用於兩個物件是否相等。Object的equals方法很簡單,只用兩個物件的引用是否相等來判斷是否是同一個物件。Java規範要求equals方法具備以下特性:
- 自反性:對於任何非空引用,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引用的物件沒有發生變化,反覆呼叫x.equals(y)應該返回同樣的結果;
- 對於任意非空引用x,x.equals(null)應該返回false。
Object類的equals實現為:
public boolean equals(Object obj) {
return (this == obj); // 大佬都不嫌麻煩用括號擴起,習慣使然!
}
複製程式碼
然而在大多數情況下,只將引用作為判斷物件的唯一標準,太嚴格且沒有實際意義。例如對於大多數實體類,如Book類,我們對比兩個Book類例項時,關注的是這兩個例項的狀態(書名、價格、作者等)是否相等。如果兩者的狀態都相等,即使不是同一個引用,我們也判定這兩個Book例項相等。下面實現一個判斷兩個Book例項是否相等的equals方法:
public class Book {
private String name; // 書名
private int price; // 價格
private String writer; // 作者
@Override // 1. 帶註解,防止入參型別錯誤
public boolean equals(Object otherObject){
// 2. 檢測兩個例項引用是否相同,優化需要。
if (this == otherObject){
return true;
}
// 3. 檢測被比較物件是否為空,必須
if (otherObject == null){
return false;
}
// 4. 比較this和入參是否同屬於一個類
if (getClass() != otherObject.getClass()){
return false;
}
// 5. 將otherObject轉換為相應的類型別變數,為後續比較具體域狀態做準備
Book other = (Book) otherObject;
// 6. 比較所有的域。有物件域可能都為null,因此不能使用name.equals(other.name)這種方法
return Objects.equals(name, other.name)
&& price == other.price
&& Objects.equals(writer, other.writer);
}
}
複製程式碼
在程式碼的第4步中,使用getClass()
來判斷兩個物件是否同屬於一個類,其使用場景為:equals的定義在每個子類中有所改變。如果所有的子類都擁有統一的語義,如所有的子類都使用父類的equals方法,則使用instanceof
代替getClass()
:
if (!(otherObject instanceof Book)) {
return false;
}
複製程式碼
equals在使用時,要格外注意當父類實現了equals方法,子類之間equals方法的實現方式要滿足對稱性的要求。
另外值得注意的是,如果重寫equals方法,則必須同時重新定義hashCode()方法,以便使用者可以將物件插入到雜湊表中。
hashCode()
方法
Object類中的hashCode方法是native方法,返回一個整型數值(也可以是負數),無具體的實現方式。由於該方法是在Object中定義的,因此java中每個物件都會有一個預設的雜湊碼(hash code),其值可能是物件的儲存地址。
public native int hashCode();
複製程式碼
hashCode方法具體分析考慮之後結合集合再整理,先佔個坑。TODO...
toString()
方法
Object的該方法返回了類的名稱加上'@',再加上此類雜湊碼的16進位制表示,如:I[@1a46e30,其中I[表示一個整型陣列。
public String toString(){
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
複製程式碼
一個好的習慣,應該是為每個類,特別是模型類重寫一個toString()方法,以便使用者能夠通過這個方法獲得物件狀態的必要資訊。一個反面例子就是陣列沒有重寫toString(),而是直接繼承了Object的toString()方法,因此陣列物件呼叫toString()方法,列印的字串都是類似I[@1a46e30這種形式的,雞肋!所以無奈只能使用靜態方法Arrays.toString(arr)
或者Arrays.deepToString()
方法。
絕對多數的toString方法都遵循這樣的格式:類的名字,隨後是一對方括號括起來的閾值。
下面是Person類中toString方法的實現:
public String toString() {
return getClass().getName()
+ "[name=" + name
+ ", age=" + age
+ "]";
}
複製程式碼
toString在專案被頻繁使用,特別是只要物件與一個字串通過操作符"+"連線起來,或作為System.out.println()
的入參,則編譯器都會預設呼叫物件的toString方法。