一文搞懂Java克隆及深拷貝與淺拷貝的區(qū)別
什么是克隆,為什么在編程中使用克隆
克隆是指創(chuàng)建一個對象的副本,使得新創(chuàng)建的對象在內容上與原始對象相同。在編程中,克隆是常用的技術之一,它具有以下幾個重要用途和優(yōu)勢:
復制對象:使用克隆可以創(chuàng)建一個與原始對象相同的新對象,包括對象的屬性和狀態(tài)。這樣可以在不影響原始對象的情況下,對新對象進行修改、操作、傳遞等。這在某些場景下非常有用,可以避免重新創(chuàng)建和初始化一個對象。
隔離性與保護:通過克隆,可以創(chuàng)建一個獨立于原始對象的副本。這樣,修改克隆對象時,不會影響到原始對象,從而實現(xiàn)了對象之間的隔離性。這對于多線程環(huán)境下的并發(fā)操作或者保護重要數(shù)據(jù)具有重要意義。
性能優(yōu)化:有時候,通過克隆對象可以提高程序的性能。在某些場景下,對象的創(chuàng)建和初始化過程可能較為耗時,如果需要多次使用這個對象,通過克隆原始對象可以避免重復的創(chuàng)建和初始化過程,從而提高程序的執(zhí)行效率。
原型模式:克隆在設計模式中有一個重要的角色,即原型模式。原型模式通過克隆來創(chuàng)建對象的實例,而不是使用傳統(tǒng)的構造函數(shù)。這樣可以提供更靈活的對象創(chuàng)建方式,并且避免了頻繁的子類化。
在編程中,通常通過實現(xiàn)Cloneable接口和重寫clone方法來實現(xiàn)對象的克隆。然而,需要注意的是克隆操作可能存在深拷貝和淺拷貝的區(qū)別,在使用時需要根據(jù)實際需求選擇合適的克隆方式。
什么是深拷貝和淺拷貝
深拷貝(Deep Copy)和淺拷貝(Shallow Copy)是在克?。–lone)操作中經常遇到的兩個概念,它們描述了克隆操作對于對象內部引用的處理方式。
淺拷貝(Shallow Copy):
- 淺拷貝指在克隆操作中,只復制對象本身以及對象內部的基本數(shù)據(jù)類型的屬性,而不復制對象內部的引用類型的屬性。
- 淺拷貝僅僅創(chuàng)建了一個新對象,該對象與原始對象共享對同一引用類型屬性的訪問。如果原始對象的引用類型屬性被修改,淺拷貝的對象也會受到影響。
- 在淺拷貝中,新對象和原始對象指向同一塊內存區(qū)域,因此對其中一個對象進行修改可能會影響到另一個對象。
深拷貝(Deep Copy):
- 深拷貝指在克隆操作中,除了復制對象本身以及對象內部的基本數(shù)據(jù)類型的屬性外,還要遞歸地復制對象內部的引用類型的屬性。即深度克隆了所有引用類型的屬性。
- 深拷貝創(chuàng)建了一個完全獨立的新對象,該對象與原始對象沒有任何關聯(lián),對新對象和原始對象的修改互不影響。
- 在深拷貝中,新對象和原始對象分別對應不同的內存區(qū)域,它們之間不存在引用關系,因此修改其中一個對象不會影響到另一個對象。
為了實現(xiàn)深拷貝,需要對對象內部的引用類型屬性進行遞歸復制。常見的實現(xiàn)深拷貝的方式包括:
- 通過序列化和反序列化:將對象序列化為字節(jié)流,然后再反序列化為新的對象,這樣可以創(chuàng)建一個與原始對象完全獨立的副本。
- 通過逐個復制引用類型屬性:對于每個引用類型的屬性,創(chuàng)建一個新的實例并將原始對象屬性的內容復制到新的實例中。
需要注意的是,并非所有對象都能進行深拷貝。某些對象或者類中的屬性可能是不可變的,無需拷貝;某些對象可能包含循環(huán)引用,無法完全復制。因此,在進行克隆操作時,需要根據(jù)具體情況選擇合適的拷貝方式。
深拷貝和淺拷貝的主要區(qū)別在于對于對象內部引用類型屬性的處理方式。
數(shù)據(jù)復制層次的深度:
- 淺拷貝只復制對象本身以及對象內部的基本數(shù)據(jù)類型的屬性,不會遞歸地復制引用類型的屬性。因此,在淺拷貝中,新對象和原始對象共享對同一引用類型屬性的訪問。
- 深拷貝除了復制對象本身和基本數(shù)據(jù)類型的屬性外,還會遞歸地復制對象內部的引用類型的屬性。這樣,深拷貝創(chuàng)建了一個完全獨立的新對象,與原始對象沒有任何關聯(lián)。
對象之間的關聯(lián)性:
- 淺拷貝得到的新對象與原始對象共享對同一引用類型屬性的訪問。如果對其中一個對象的引用類型屬性進行修改,另一個對象也會受到影響。
- 深拷貝得到的新對象與原始對象沒有任何關聯(lián),修改其中一個對象的引用類型屬性不會影響到另一個對象。
內存區(qū)域的分配:
- 在淺拷貝中,新對象和原始對象指向同一塊內存區(qū)域。因此,對其中一個對象進行修改可能會影響到另一個對象。
- 在深拷貝中,新對象和原始對象分別對應不同的內存區(qū)域,它們之間不存在引用關系,因此修改其中一個對象不會影響到另一個對象。
淺拷貝示例
實現(xiàn) Cloneable 接口和重寫 clone() 方法:
- Java 中的 Cloneable 接口是一個標記接口,沒有定義任何方法。通過實現(xiàn) Cloneable 接口并重寫 clone() 方法,可以實現(xiàn)對象的淺拷貝。
- 在 clone() 方法中,調用父類的 clone() 方法,并將其返回值進行類型轉換即可完成淺拷貝。
下面是一個示例代碼,演示了如何使用 Cloneable 接口和 clone() 方法實現(xiàn)淺拷貝:
class Person implements Cloneable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Main { public static void main(String[] args) { Person person1 = new Person("Alice", 25); try { // 淺拷貝 Person person2 = (Person) person1.clone(); System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25 System.out.println(person2.getName() + " " + person2.getAge()); // Alice 25 person2.setName("Bob"); person2.setAge(30); System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25 System.out.println(person2.getName() + " " + person2.getAge()); // Bob 30 } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }
在上述示例中,我們創(chuàng)建了一個 Person 類,并實現(xiàn)了 Cloneable 接口。在 clone() 方法中直接調用了父類的 clone() 方法,并進行了類型轉換。通過調用 clone()
方法,可以得到一個新的對象 person2
,它與原始對象 person1
具有相同的屬性值。當修改 person2
的屬性時,不會影響到 person1
。
深拷貝示例
使用序列化和反序列化:
- 將對象寫入到字節(jié)流中,然后再從字節(jié)流中讀取出來,這個過程會重新創(chuàng)建一個完全獨立的對象,實現(xiàn)了深拷貝。
- 為了實現(xiàn)深拷貝,需要將對象及其關聯(lián)的對象都實現(xiàn)序列化。
下面是一個示例代碼,演示了如何使用序列化和反序列化實現(xiàn)深拷貝:
import java.io.*; class Address implements Serializable { private String city; private String street; public Address(String city, String street) { this.city = city; this.street = street; } public void setCity(String city) { this.city = city; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public String getStreet() { return street; } } class Person implements Serializable { private String name; private int age; private Address address; public Person(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setAddress(Address address) { this.address = address; } public String getName() { return name; } public int getAge() { return age; } public Address getAddress() { return address; } } public class Main { public static void main(String[] args) { Address address = new Address("City", "Street"); Person person1 = new Person("Alice", 25, address); // 深拷貝 Person person2 = deepCopy(person1); System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 City System.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Alice 25 City person2.setName("Bob"); person2.setAge(30); person2.getAddress().setCity("New City"); System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 City System.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Bob 30 New City } public static <T extends Serializable> T deepCopy(T object) { try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); objectOutputStream.flush(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (T) objectInputStream.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); return null; } } }
在上述示例中,我們創(chuàng)建了一個 Address 類和一個 Person 類,它們都實現(xiàn)了 Serializable 接口。通過序列化和反序列化操作,我們可以實現(xiàn)深拷貝。在 deepCopy()
方法中,我們使用字節(jié)流將對象寫入到內存中,并從內存中讀取出來,從而得到一個新的獨立對象。通過調用 deepCopy()
方法,可以得到一個新的對象 person2
,它與原始對象 person1
完全獨立。在修改 person2
的屬性時,不會影響到 person1
。 值得注意的是,要實現(xiàn)深拷貝,所有相關的類都需要實現(xiàn) Serializable 接口。
深拷貝和淺拷貝的區(qū)別
深拷貝(Deep Copy):
適用場景:
- 當源對象包含引用類型的屬性時,如果需要復制對象及其子對象的所有屬性,而不僅僅只是復制引用,就需要使用深拷貝。
- 當希望修改副本對象的屬性不影響原始對象時,需要使用深拷貝。
工作原理:
- 深拷貝將源對象及其關聯(lián)的全部對象進行遞歸復制,每個對象都擁有獨立的內存空間,修改副本對象不會影響原始對象。
實現(xiàn)方式:
- 使用遞歸或者拷貝構造函數(shù)來復制對象及其子對象的屬性。
示例場景:
- 復制復雜對象的副本,使其成為獨立的個體,例如:拷貝一個包含集合、嵌套對象等的數(shù)據(jù)結構。
- 對象圖的克隆,當原對象包含子對象,并且對子對象的修改不應該影響原對象時。
淺拷貝(Shallow Copy):
適用場景:
- 當源對象的屬性全為基本數(shù)據(jù)類型或者不可變對象,并且不需要復制引用類型的屬性時,可以使用淺拷貝。
- 當希望修改副本對象的屬性同時影響原始對象時,可以使用淺拷貝。
工作原理:
- 淺拷貝只復制對象及其引用,而不復制引用指向的實際對象,新舊對象將共享同一個引用對象。修改副本對象會影響原始對象。
實現(xiàn)方式:
- 通常使用對象的
clone()
方法來進行淺拷貝。
- 通常使用對象的
示例場景:
- 快速創(chuàng)建對象副本,以便在某些操作中對其進行修改,同時保留原始對象。
- 在某些情況下,共享一部分數(shù)據(jù)以節(jié)省內存和提高性能。
以上就是一文搞懂Java克隆技術及深拷貝與淺拷貝的區(qū)別的詳細內容,更多關于Java克隆技術及深拷貝與淺拷貝的資料請關注腳本之家其它相關文章!
相關文章
SpringData JPA基本/高級/多數(shù)據(jù)源的使用詳解
這篇文章主要介紹了SpringData JPA基本/高級/多數(shù)據(jù)源的使用詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02解決java編譯錯誤( 程序包javax.servlet不存在javax.servlet.*)
這篇文章主要介紹了解決java編譯錯誤的相關資料,主要解決 程序包javax.servlet不存在javax.servlet.*的問題,需要的朋友可以參考下2017-08-08MyBatis實現(xiàn)動態(tài)SQL更新的代碼示例
本文博小編將帶領大家學習如何利用 MyBatis 攔截器機制來優(yōu)雅的實現(xiàn)這個需求,文中通過代碼示例介紹的非常詳細,具有一定的參考價值,需要的朋友可以參考下2023-07-07使用concurrentHashMap如何實現(xiàn)緩存
文章介紹了使用ConcurrentHashMap實現(xiàn)緩存的線程安全性和初始化方法,以及如何處理高并發(fā)場景下的緩存清理和數(shù)據(jù)一致性問題,包括分桶、使用ConcurrentLinkedQueue以及使用CountDownLatch來確保緩存數(shù)據(jù)的不丟失2025-02-02springboot接收http請求,解決參數(shù)中+號變成空格的問題
這篇文章主要介紹了springboot接收http請求,解決參數(shù)中+號變成空格的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08SpringBoot中的多RabbitMQ數(shù)據(jù)源配置實現(xiàn)
本篇博客將介紹如何在 Spring Boot 中配置和管理多個 RabbitMQ 數(shù)據(jù)源,以滿足不同的應用需求,具有一定的參考價值,感興趣的可以了解一下2023-09-09