java.lang.Object 是 Java 所有類的直接或間接父類,Java 中每個類都默認繼承 Object 類(即使你沒寫 extends Object)。
Object 中的常用方法:
方法名 | 功能簡介 |
---|---|
toString() | 返回對象的字符串表示 |
equals(Object) | 判斷兩個對象是否“邏輯相等” |
hashCode() | 返回哈希值,常用于集合類 |
getClass() | 返回此對象的運行時類 |
clone() | 克隆對象(實現 Cloneable 時有效) |
finalize() | 對象被 GC 回收前調用(不推薦使用) |
toString
我們使用 System.out.println() 打印對象,調用的就是對象的 toString 方法,將對象的 toString 方法返回的結果打印出來。
public class Person {private String name;public Person(String name) {this.name = name;}// 默認輸出是:類名@哈希值// 重寫 toString() 提供可讀性@Overridepublic String toString() {return "Person[name=" + name + "]";}
}
equals
equals 方法是用來比較兩個對象是否邏輯相等,Object 類的 equals 方法直接比較的是兩個對象地址。我們一般需要修改 equals 方法,比如:兩個公民對象,如果身份證 id 是一樣的,從我們的現實生活的經驗中判斷,這兩個人肯定是同一個人,即他們是相等的。
對于基本類型,我們可以直接使用 == 比較他們是不是相等的,如果他們的值是相等的,==
返回 true,否則返回 false,對于對象,我們需要使用 equals 方法判斷兩個對象是不是相等的。
重寫 equals 方法的要點,要遵循如下的特性:
- 自反性:x.equals(x) 必須返回 true
- 對稱性:x.equals(y) 和 y.equals(x) 必須返回相同結果
- 傳遞性:如果 x.equals(y)且y.equals(z),則 x.equals(z) 必須為 true
- 一致性:多次調用 x.equals(y) 應該返回相同結果
- 非空性:x.equals(null) 必須返回 false
重寫 equals() 方法后,必須重寫 hashCode() 方法,以維護 Java 對象的通用約定,并確保基于哈希的集合(如 HashSet、HashMap 和 HashTable)能夠正常工作。Java 的 Object 類對 equals() 和 hashCode() 方法之間有明確的約定:
- 如果兩個對象根據 equals() 方法相等,那么它們的 hashCode() 方法必須返回相同的值
- 如果兩個對象的 hashCode() 方法返回相同的值,它們根據 equals() 方法不一定相等(哈希沖突)
- 哈希表的功能: 哈希表使用 hashCode() 方法來確定對象在表中的位置(桶)。如果 equals() 被重寫但 hashCode() 沒有,則兩個邏輯上相等的對象(equals() 返回 true)可能具有不同的哈希碼。這會導致以下問題:1. 無法找到對象: 當你試圖在 HashSet 中查找一個對象時,HashSet 會使用對象的 hashCode() 來定位它所在的桶。如果 hashCode() 與原始對象的 hashCode() 不同(即使它們邏輯上相等),HashSet 將無法找到該對象,即使它已經存在于集合中。2. 重復元素: HashSet 旨在防止重復元素。如果兩個邏輯上相等的 對象具有不同的哈希碼,HashSet 會將它們視為不同的對象,從而允許將重復元素添加到集合中。3. HashMap 的不一致性: HashMap 也依賴于 hashCode() 和 equals() 來正確存儲和檢索鍵值對。如果鍵的 hashCode() 和 equals() 不一致,HashMap 的行為將變得不可預測。
下面是一個重寫 equals 方法的例子:
public class Person {private String name;public Person(String name) {this.name = name;}// 重寫equals方法@Overridepublic boolean equals(Object obj) {// 1. 檢查是否是同一個對象if (this == obj) {return true;}// 2. 檢查是否為null或類型不同if (obj == null || getClass() != obj.getClass()) {return false;}// 3. 類型轉換Person person = (Person) obj;// 4. 比較關鍵字段return name != null ? name.equals(person.name) : person.name == null;}// 重寫equals時也應該重寫hashCode@Overridepublic int hashCode() {return name != null ? name.hashCode() : 0;}
}
Person p1 = new Person("Tom");
Person p2 = new Person("Tom");// == 比較的是地址
System.out.println(p1 == p2); // false// 默認 equals 也是比較地址
// 可通過重寫實現“內容相等”
System.out.println(p1.equals(p2)); // true(需重寫后)
hashCode
hashCode 方法返回對象的 hashCode 編碼,在上面的 equals 方法中我們講過,要確保兩個對象如果 equals 方法判斷他們是相等的,那么他們的 hashCode 方法返回的哈希碼也必須是相等的。
// 在集合類(如 HashMap、HashSet)中要重寫 hashCode 與 equals 保持一致性
@Override
public int hashCode() {return name.hashCode(); // 簡單寫法
}
getClass
getClass 方法返回的是此 Object 的運行時類。
Person p = new Person("Jerry");
System.out.println(p.getClass().getName()); // 輸出類的全名
clone
按照慣例,如果一個類希望能夠被克隆,它應該重寫Object.clone()
方法,并將其訪問修飾符改為public
,通常返回它自己的類型(需要進行類型轉換)。在重寫的方法中,通常會先調用super.clone()
來獲得一個基本的副本。
Object.clone()
的默認行為是執行淺拷貝。 這里涉及到連個概念,淺拷貝(Shallow Copy)與深拷貝(Deep Copy)。
淺拷貝:
- 對于原始數據類型(
int
,double
,boolean
等),直接復制值。 - 對于引用類型(對象引用),復制的是引用本身,而不是被引用的對象。這意味著原始對象和克隆對象中的引用字段將指向內存中的同一個對象。
- 后果: 如果原始對象或克隆對象修改了共享引用對象的狀態,這種改變會同時反映在另一個對象中。在大多數情況下,這可能不是期望的行為,因為它打破了克隆對象與原始對象之間的獨立性。
深拷貝:
- 為了實現深拷貝,你需要在重寫的
clone()
方法中,對所有引用類型的字段(如果這些字段也是可克隆的)手動調用它們的clone()
方法,從而創建這些引用對象的獨立副本。 - 復雜性: 如果對象包含多層嵌套的引用類型,實現深拷貝可能會變得非常復雜,因為你需要遞歸地克隆所有被引用的對象。
class Address implements Cloneable {String city;String street;public Address(String city, String street) {this.city = city;this.street = street;}@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone(); // Address類的淺拷貝}@Overridepublic String toString() {return "Address [city=" + city + ", street=" + street + "]";}
}class Student implements Cloneable {String name;int age;Address address; // 引用類型public Student(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}// 淺拷貝的實現@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone(); // 默認是淺拷貝}// 深拷貝的實現(需要手動處理引用類型字段)public Object deepClone() throws CloneNotSupportedException {Student clonedStudent = (Student) super.clone();// 對引用類型字段進行深拷貝clonedStudent.address = (Address) address.clone();return clonedStudent;}@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + ", address=" + address + "]";}
}public class CloneDemo {public static void main(String[] args) throws CloneNotSupportedException {Address originalAddress = new Address("New York", "Broadway");Student originalStudent = new Student("Alice", 20, originalAddress);// 淺拷貝示例Student shallowClonedStudent = (Student) originalStudent.clone();System.out.println("Original Student (Shallow): " + originalStudent);System.out.println("Shallow Cloned Student: " + shallowClonedStudent);// 修改淺拷貝后的地址,觀察原始對象的變化shallowClonedStudent.address.city = "Los Angeles";System.out.println("After changing shallowClonedStudent's city:");System.out.println("Original Student (Shallow): " + originalStudent); // Original也會改變System.out.println("Shallow Cloned Student: " + shallowClonedStudent);System.out.println("---");// 深拷貝示例Address originalAddress2 = new Address("London", "Oxford Street");Student originalStudent2 = new Student("Bob", 22, originalAddress2);Student deepClonedStudent = (Student) originalStudent2.deepClone();System.out.println("Original Student (Deep): " + originalStudent2);System.out.println("Deep Cloned Student: " + deepClonedStudent);// 修改深拷貝后的地址,觀察原始對象的變化deepClonedStudent.address.city = "Paris";System.out.println("After changing deepClonedStudent's city:");System.out.println("Original Student (Deep): " + originalStudent2); // Original不會改變System.out.println("Deep Cloned Student: " + deepClonedStudent);}
}
盡管clone()
方法提供了對象復制的功能,但它在實際開發中很少被推薦使用,并且存在一些缺點:
- 破壞封裝性:
clone()
方法需要訪問對象的內部狀態,這可能違反封裝原則。 **Cloneable**
接口的不足:Cloneable
是一個標記接口,它沒有定義clone()
方法。這意味著編譯器無法檢查你是否正確地重寫了clone()
方法,或者是否處理了CloneNotSupportedException
。- 不調用構造函數:
clone()
方法通過直接復制內存來創建對象,不調用任何構造函數。這可能導致一些需要構造函數來正確初始化的邏輯被跳過。 - 不可變對象問題: 對于包含
final
字段的對象,clone()
方法無法修改這些final
字段,因為它們只能在構造函數中初始化。 - 鏈式克隆的復雜性: 當一個對象包含其他對象的引用時,為了實現深拷貝,你必須確保所有被引用的類也實現了
Cloneable
并正確地重寫了clone()
方法。這會導致一個“病毒式”的Cloneable
實現。 - 異常處理:
Object.clone()
會拋出受檢查異常CloneNotSupportedException
,即使你的類實現了Cloneable
,也需要在簽名中聲明或捕獲它。
鑒于這些缺點,通常有更好的替代方案來實現對象復制:
- 拷貝構造函數(Copy Constructor):創建一個接收同類型對象作為參數的構造函數,并在其中手動復制字段。 這是最常見和推薦的方式,它提供了更好的控制,允許你選擇是進行淺拷貝還是深拷貝,并且可以處理
final
字段。 - 拷貝工廠方法(Copy Factory Method): 提供一個靜態工廠方法來創建對象的副本。
- 序列化/反序列化(Serialization/Deserialization): 通過將對象序列化到字節流(例如內存中的
ByteArrayOutputStream
),然后從該字節流反序列化回來,可以實現深拷貝。 優點是簡單,自動處理深拷貝。 缺點是性能開銷較大,且所有涉及的類都必須實現Serializable
接口。 - 第三方庫: 許多現代框架和庫提供了更強大、更靈活的對象映射和復制工具(如Apache Commons Lang的
SerializationUtils.clone()
)。