Java 中的 Object 類別
1. 概述
在Java中, Object類別是類別層次結構的根。 Java中的每個類別都是Object的直接或間接子類別。因此,所有物件(包括數組)都繼承了Object類別的方法實現,並且可以根據需要重寫這些方法。
在本快速概覽指南中,我們將詳細討論Object類別。
2. Object類別的重要性
Object類別之所以重要,原因有以下幾點:
- 通用基底類別:它為所有物件提供了一個通用類型,因此
Object類型的變數可以持有對任何物件的參考。假設我們有一個Car類別。那麼,我們可以這樣寫:Object obj = new Car();。 - 多態性:
Object中定義的方法(例如equals和toString)建立了一個所有物件都必須遵守的契約,從而允許與各種類型的物件進行一致的互動。 - 集合框架:Java 集合框架中的許多類,如
ArrayList和HashMap,都設計用於處理Object引用,嚴重依賴equals()和hashCode()的正確實作。
3. Object類別的關鍵方法
Object類別提供了其他所有類別都繼承的幾個方法:
| 方法 | 目的 | 筆記 |
|---|---|---|
equals(Object obj) |
表示其他物件是否與此物件「相等」。 | 通常,我們應該重寫此方法,以便對物件的狀態(內容)進行有意義的比較,而不僅僅是對記憶體位址進行比較。 |
hashCode() |
傳回物件的雜湊碼值。 | 無論何時重寫equals()都必須重寫hashCode() ,以確保兩個相等的物件具有相同的雜湊碼,這對於HashMap和HashSet等集合至關重要。 |
toString() |
傳回物件的字串表示形式。 | 預設實作傳回一個字串,其中包含類別名稱、「@」符號和物件哈希碼的無符號十六進位表示形式。我們應該重寫此實作以描述物件的內容。 |
getClass() |
傳回此Object的運行時類別。 |
我們可以使用傳回的Class物件進行反射-在執行時檢查或修改類別的行為。 |
clone() |
建立並傳回此物件的副本。 | 要求類別實作Cloneable介面。預設實作執行的是淺拷貝。通常我們需要重寫該實作並處理CloneNotSupportedException 。 |
wait(), notify(), notifyAll() |
用於線程間通信和同步。 | 這些方法通常在與物件關聯的監視器(鎖)上調用,並在同步程式碼區塊或方法中使用。 |
finalize() |
當垃圾回收器確定不再有任何物件參考時,該物件會被垃圾回收器呼叫。 | 此方法已棄用,不應再用於資源清理。現代 Java 實作更傾向於使用try-with-resources或其他明確的清理機制。 |
4. 範例類
我們來使用一個簡單的Car類,它重寫了幾個標準的Object方法。為了正確支援clone()方法, Car類別必須實作標記介面Cloneable並重寫受保護的clone()方法:
public class Car implements Cloneable {
private String make;
private int year;
public Car(String make, int year) {
this.make = make;
this.year = year;
}
public String getMake() {
return make;
}
public int getYear() {
return year;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Car car = (Car) obj;
return year == car.year && Objects.equals(make, car.make);
}
@Override
public int hashCode() {
return Objects.hash(make, year);
}
@Override
public String toString() {
return "Car{"
+ "make='" + make + '\''
+ ", year=" + year
+ '}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
因此,該類別使用了equals() 、 hashCode()和toString()的標準實作。此外,它重寫了Object中的受保護的clone()方法以執行淺拷貝。這是實現Cloneable標記介面時的標準模式。
5. 探索方法
讓我們使用 JUnit 測試來示範這些方法的行為。
5.1. equals(Object obj)
首先,我們驗證**equals()方法滿足自反性、對稱性、傳遞性和一致性**:
@Test
void givenACarObject_whenTestingObjectEqualsItself_thenEqualsReflexive() {
Car car1 = new Car("Honda", 2020);
assertTrue(car1.equals(car1));
}
@Test
void givenTwoCarObjects_whenTestingSymmetric_thenEqualsSymmetric() {
Car car1 = new Car("Honda", 2020);
Car car2 = new Car("Honda", 2020);
assertTrue(car1.equals(car2));
assertTrue(car2.equals(car1);
}
@Test
void givenThreeCarObjects_whenTestingTransitive_thenEqualsTransitive() {
Car car1 = new Car("Honda", 2020);
Car car2 = new Car("Honda", 2020);
Car car3 = new Car("Honda", 2020);
assertTrue(car1.equals(car2));
assertTrue(car2.equals(car3));
assertTrue(car1.equals(car3));
}
@Test
void givenTwoDifferentCarObjects_whenComparingWithEquals_thenEqualsReturnsFalse() {
Car car1 = new Car("Honda", 2020);
Car car2 = new Car("Toyota", 2020);
assertFalse(car1.equals(car2));
}
@Test
void givenANonNullCarObject_whenTestingAgainstNull_thenEqualsReturnsFalse() {
Car car1 = new Car("Honda", 2020);
assertFalse(car1.equals(null));
}
從這些例子我們可以看出,「自反性」意味著一個物件等於它自己。此外,「對稱性」意味著如果car1.equals(car2)為true ,那麼car2.equals(car1)也為true 。另外,「傳遞性」意味著如果a==b且b==c ,那麼a==c 。接下來,我們驗證了具有不同狀態的物件不相等的一致性。最後,我們看到equals()對null輸入回傳false 。
5.2. hashCode()
hashCode()方法確保如果兩個物件根據equals()方法判斷為相等,則對每個物件呼叫hashCode()會產生相同的整數結果;相等的物件必須具有相同的雜湊碼:
@Test
void givenTwoEqualCarObjects_whenComparingHashCodes_thenReturnsEqualHashCodes() {
Car car1 = new Car("Honda", 2020);
Car car2 = new Car("Honda", 2020);
assertEquals(car1.hashCode(), car2.hashCode());
}
@Test
void givenACarObject_whenTestingHashCodeConsistency_thenReturnsSameHashCodeAcrossMultipleCalls() {
Car car = new Car("Honda", 2020);
int initialHash = car.hashCode();
assertEquals(initialHash, car.hashCode());
}
5.3. toString()
toString()方法應該傳回一個字串,該字串簡潔明了地表示物件的內容。讓我們透過一個簡單的測試來確認它是否傳回了預期的字串:
@Test
void givenACarObject_whenTestingToString_thenReturnsExpectedString() {
Car car = new Car("Tesla", 2023);
String expected = "Car{make='Tesla', year=2023}";
assertEquals(expected, car.toString());
}
5.4. getClass()
讓我們驗證一下**getClass()方法是否傳回物件的執行時間類型**:
@Test
void givenACarObject_whenTestingGetClass_thenReturnsCarClass() {
Car car = new Car("Ford", 2015);
assertEquals(Car.class, car.getClass());
}
呼叫getClass()方法會傳回Car.class類別物件。 getClass getClass()方法只是取得物件類別名稱的眾多方法之一。
5.5. clone()
接下來,我們測試**clone()是否建立一個新物件**(位於不同的記憶體位址),但內容與原始物件相同:
@Test
void givenACarObject_whenTestingClone_thenCloneSuccess throws CloneNotSupportedException {
Car original = new Car("Honda", 2020);
Car cloned = (Car) original.clone();
assertNotSame(original, cloned);
assertEquals(original, cloned);
assertEquals(original.getMake(), cloned.getMake());
assertEquals(original.getYear(), cloned.getYear());
}
請注意,由於clone()的回傳類型為Object ,因此我們必須將回傳值強制轉換為Car類別。
5.6. wait() 、 notify()和notifyAll()
wait() 、 notify()和notifyAll()方法用於多執行緒環境中的執行緒同步。我們需要避免並發問題,防止多個執行緒同時嘗試修改同一個Object實例。
5.7. finalize()
finalize()方法會在特定物件的垃圾回收之前呼叫。請注意,此方法在 Java 9 中已被棄用,並且可能會在未來的 Java 版本中移除。
6. 結論
本文詳細探討了Java中的Object類別。我們先解釋了Object的重要性,然後介紹了它的各種方法。最後,我們使用一個名為Car的範例類別來示範這些方法的行為。
與往常一樣,範例的完整程式碼可在 GitHub 上找到。