深拷貝與淺拷貝的關鍵區別在于是否復制對象內部的引用對象。1. 淺拷貝僅復制對象的非引用類型字段,引用類型字段則共享同一地址,修改一個對象的引用字段會影響其他對象;2. 深拷貝遞歸復制所有引用對象,生成完全獨立的新對象,修改新對象不影響原對象。3. 實現深拷貝的方式包括手動遞歸復制、重寫 clone() 方法、序列化與反序列化、使用第三方庫等。4. 選擇拷貝方式需根據場景決定:淺拷貝適用于引用對象不可變或需要共享的情況,深拷貝適用于需完全獨立的場景。5. 實現深拷貝時需注意循環引用問題,可通過緩存已復制對象避免無限遞歸。6. 深拷貝性能開銷較大,在高性能場景中應謹慎使用或采用優化策略。
深拷貝和淺拷貝,關鍵在于拷貝對象時,是否復制了對象內部的引用對象。淺拷貝只復制引用,深拷貝則會遞歸地復制所有引用對象,生成全新的獨立對象。
深拷貝和淺拷貝的區別,本質上是對對象內部引用類型的處理方式不同。
淺拷貝的陷阱:共享的引用
淺拷貝,也叫影子拷貝,創建新對象時,只復制原始對象的非引用類型字段的值。對于引用類型字段,新對象只是復制了引用地址,這意味著新對象和原始對象共享同一個引用對象。修改其中一個對象的引用對象,另一個對象也會受到影響。例如:
立即學習“Java免費學習筆記(深入)”;
class Address { String city; public Address(String city) { this.city = city; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } } class Person { String name; Address address; public Person(String name, Address address) { this.name = name; this.address = address; } public String getName() { return name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } // 淺拷貝方法 public Person shallowcopy() { return new Person(this.name, this.address); } } public class Main { public static void main(String[] args) { Address address = new Address("Beijing"); Person person1 = new Person("Alice", address); Person person2 = person1.shallowCopy(); // 修改 person2 的地址 person2.getAddress().setCity("Shanghai"); System.out.println("Person1's city: " + person1.getAddress().getCity()); // 輸出 Shanghai System.out.println("Person2's city: " + person2.getAddress().getCity()); // 輸出 Shanghai } }
可以看到,修改person2的地址后,person1的地址也跟著改變了。這就是淺拷貝的副作用,多個對象共享同一個內部引用對象。
實現深拷貝的幾種方式
深拷貝則會完全復制原始對象及其所有引用對象,生成一個全新的、完全獨立的對象。修改新對象不會影響原始對象。實現深拷貝的方法有幾種:
- 手動實現: 遞歸地復制每一個引用類型的字段。這是最直接但也是最繁瑣的方式。
class Address implements Cloneable { String city; public Address(String city) { this.city = city; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } @Override public Address clone() { try { return (Address) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } } } class Person implements Cloneable { String name; Address address; public Person(String name, Address address) { this.name = name; this.address = address; } public String getName() { return name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } // 深拷貝方法 @Override public Person clone() { try { Person cloned = (Person) super.clone(); cloned.address = this.address.clone(); // 關鍵:復制 Address 對象 return cloned; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } } public class Main { public static void main(String[] args) { Address address = new Address("Beijing"); Person person1 = new Person("Alice", address); Person person2 = person1.clone(); // 修改 person2 的地址 person2.getAddress().setCity("Shanghai"); System.out.println("Person1's city: " + person1.getAddress().getCity()); // 輸出 Beijing System.out.println("Person2's city: " + person2.getAddress().getCity()); // 輸出 Shanghai } }
-
使用 clone() 方法: 讓類實現 Cloneable 接口,并重寫 clone() 方法。但需要注意,clone() 方法默認是淺拷貝,需要手動實現深拷貝邏輯。上面的代碼示例已經展示了如何使用clone()方法實現深拷貝。
-
序列化和反序列化: 將對象序列化成字節流,再反序列化成新的對象。這種方式可以實現完全的深拷貝,但性能相對較低。需要類實現 Serializable 接口。
import Java.io.*; class Address implements Serializable { String city; public Address(String city) { this.city = city; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } } class Person implements Serializable { String name; Address address; public Person(String name, Address address) { this.name = name; this.address = address; } public String getName() { return name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } // 深拷貝方法 (使用序列化) public Person deepCopy() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (Person) ois.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return null; } } } public class Main { public static void main(String[] args) { Address address = new Address("Beijing"); Person person1 = new Person("Alice", address); Person person2 = person1.deepCopy(); // 修改 person2 的地址 person2.getAddress().setCity("Shanghai"); System.out.println("Person1's city: " + person1.getAddress().getCity()); // 輸出 Beijing System.out.println("Person2's city: " + person2.getAddress().getCity()); // 輸出 Shanghai } }
- 使用第三方庫: 如 apache Commons Lang 的 SerializationUtils 或 Jackson 等庫,可以簡化序列化和反序列化的過程。
何時使用深拷貝,何時使用淺拷貝?
選擇深拷貝還是淺拷貝,取決于具體的應用場景和需求。
- 淺拷貝: 適用于對象內部的引用對象是不可變的(immutable),或者多個對象共享同一個引用對象是期望行為的場景。例如,字符串常量池中的字符串。
- 深拷貝: 適用于需要創建完全獨立的副本,避免修改一個對象影響到其他對象的場景。例如,在多線程環境下,需要保證數據的獨立性。
避免循環引用導致的深拷貝問題
在實現深拷貝時,需要特別注意循環引用的問題。如果對象之間存在循環引用,遞歸復制會導致無限循環,最終導致棧溢出。解決循環引用的方法是使用一個緩存來記錄已經復制過的對象,避免重復復制。例如,可以使用 IdentityHashMap 來存儲已經復制過的對象。
深拷貝的性能考量
深拷貝的性能開銷通常比淺拷貝要大得多,因為它需要遞歸地復制所有引用對象。在對性能要求較高的場景下,需要謹慎使用深拷貝,或者考慮使用其他優化策略,例如寫時復制(copy-on-write)。