Java中深拷貝和淺拷貝的區別 分析復制差異

深拷貝與淺拷貝的關鍵區別在于是否復制對象內部的引用對象。1. 淺拷貝僅復制對象的非引用類型字段,引用類型字段則共享同一地址,修改一個對象的引用字段會影響其他對象;2. 深拷貝遞歸復制所有引用對象,生成完全獨立的新對象,修改新對象不影響原對象。3. 實現深拷貝的方式包括手動遞歸復制、重寫 clone() 方法、序列化與反序列化、使用第三方庫等。4. 選擇拷貝方式需根據場景決定:淺拷貝適用于引用對象不可變或需要共享的情況,深拷貝適用于需完全獨立的場景。5. 實現深拷貝時需注意循環引用問題,可通過緩存已復制對象避免無限遞歸。6. 深拷貝性能開銷較大,在高性能場景中應謹慎使用或采用優化策略。

Java中深拷貝和淺拷貝的區別 分析復制差異

深拷貝和淺拷貝,關鍵在于拷貝對象時,是否復制了對象內部的引用對象。淺拷貝只復制引用,深拷貝則會遞歸地復制所有引用對象,生成全新的獨立對象。

Java中深拷貝和淺拷貝的區別 分析復制差異

深拷貝和淺拷貝的區別,本質上是對對象內部引用類型的處理方式不同。

Java中深拷貝和淺拷貝的區別 分析復制差異

淺拷貝的陷阱:共享的引用

淺拷貝,也叫影子拷貝,創建新對象時,只復制原始對象的非引用類型字段的值。對于引用類型字段,新對象只是復制了引用地址,這意味著新對象和原始對象共享同一個引用對象。修改其中一個對象的引用對象,另一個對象也會受到影響。例如:

立即學習Java免費學習筆記(深入)”;

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的地址也跟著改變了。這就是淺拷貝的副作用,多個對象共享同一個內部引用對象。

實現深拷貝的幾種方式

深拷貝則會完全復制原始對象及其所有引用對象,生成一個全新的、完全獨立的對象。修改新對象不會影響原始對象。實現深拷貝的方法有幾種:

  1. 手動實現: 遞歸地復制每一個引用類型的字段。這是最直接但也是最繁瑣的方式。
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     } }
  1. 使用 clone() 方法: 讓類實現 Cloneable 接口,并重寫 clone() 方法。但需要注意,clone() 方法默認是淺拷貝,需要手動實現深拷貝邏輯。上面的代碼示例已經展示了如何使用clone()方法實現深拷貝。

  2. 序列化和反序列化: 將對象序列化成字節流,再反序列化成新的對象。這種方式可以實現完全的深拷貝,但性能相對較低。需要類實現 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     } }
  1. 使用第三方庫:apache Commons Lang 的 SerializationUtils 或 Jackson 等庫,可以簡化序列化和反序列化的過程。

何時使用深拷貝,何時使用淺拷貝?

選擇深拷貝還是淺拷貝,取決于具體的應用場景和需求。

  • 淺拷貝: 適用于對象內部的引用對象是不可變的(immutable),或者多個對象共享同一個引用對象是期望行為的場景。例如,字符串常量池中的字符串
  • 深拷貝: 適用于需要創建完全獨立的副本,避免修改一個對象影響到其他對象的場景。例如,在線程環境下,需要保證數據的獨立性。

避免循環引用導致的深拷貝問題

在實現深拷貝時,需要特別注意循環引用的問題。如果對象之間存在循環引用,遞歸復制會導致無限循環,最終導致溢出。解決循環引用的方法是使用一個緩存來記錄已經復制過的對象,避免重復復制。例如,可以使用 IdentityHashMap 來存儲已經復制過的對象。

深拷貝的性能考量

深拷貝的性能開銷通常比淺拷貝要大得多,因為它需要遞歸地復制所有引用對象。在對性能要求較高的場景下,需要謹慎使用深拷貝,或者考慮使用其他優化策略,例如寫時復制(copy-on-write)。

以上就是Java中深拷貝和淺拷貝的

? 版權聲明
THE END
喜歡就支持一下吧
點贊5 分享