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