一文搞懂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ù):通過(guò)克隆,可以創(chuàng)建一個(gè)獨(dú)立于原始對(duì)象的副本。這樣,修改克隆對(duì)象時(shí),不會(huì)影響到原始對(duì)象,從而實(shí)現(xiàn)了對(duì)象之間的隔離性。這對(duì)于多線程環(huán)境下的并發(fā)操作或者保護(hù)重要數(shù)據(jù)具有重要意義。
性能優(yōu)化:有時(shí)候,通過(guò)克隆對(duì)象可以提高程序的性能。在某些場(chǎng)景下,對(duì)象的創(chuàng)建和初始化過(guò)程可能較為耗時(shí),如果需要多次使用這個(gè)對(duì)象,通過(guò)克隆原始對(duì)象可以避免重復(fù)的創(chuàng)建和初始化過(guò)程,從而提高程序的執(zhí)行效率。
原型模式:克隆在設(shè)計(jì)模式中有一個(gè)重要的角色,即原型模式。原型模式通過(guò)克隆來(lái)創(chuàng)建對(duì)象的實(shí)例,而不是使用傳統(tǒng)的構(gòu)造函數(shù)。這樣可以提供更靈活的對(duì)象創(chuàng)建方式,并且避免了頻繁的子類(lèi)化。
在編程中,通常通過(guò)實(shí)現(xiàn)Cloneable接口和重寫(xiě)clone方法來(lái)實(shí)現(xiàn)對(duì)象的克隆。然而,需要注意的是克隆操作可能存在深拷貝和淺拷貝的區(qū)別,在使用時(shí)需要根據(jù)實(shí)際需求選擇合適的克隆方式。
什么是深拷貝和淺拷貝
深拷貝(Deep Copy)和淺拷貝(Shallow Copy)是在克隆(Clone)操作中經(jīng)常遇到的兩個(gè)概念,它們描述了克隆操作對(duì)于對(duì)象內(nèi)部引用的處理方式。
淺拷貝(Shallow Copy):
- 淺拷貝指在克隆操作中,只復(fù)制對(duì)象本身以及對(duì)象內(nèi)部的基本數(shù)據(jù)類(lèi)型的屬性,而不復(fù)制對(duì)象內(nèi)部的引用類(lèi)型的屬性。
- 淺拷貝僅僅創(chuàng)建了一個(gè)新對(duì)象,該對(duì)象與原始對(duì)象共享對(duì)同一引用類(lèi)型屬性的訪問(wèn)。如果原始對(duì)象的引用類(lèi)型屬性被修改,淺拷貝的對(duì)象也會(huì)受到影響。
- 在淺拷貝中,新對(duì)象和原始對(duì)象指向同一塊內(nèi)存區(qū)域,因此對(duì)其中一個(gè)對(duì)象進(jìn)行修改可能會(huì)影響到另一個(gè)對(duì)象。
深拷貝(Deep Copy):
- 深拷貝指在克隆操作中,除了復(fù)制對(duì)象本身以及對(duì)象內(nèi)部的基本數(shù)據(jù)類(lèi)型的屬性外,還要遞歸地復(fù)制對(duì)象內(nèi)部的引用類(lèi)型的屬性。即深度克隆了所有引用類(lèi)型的屬性。
- 深拷貝創(chuàng)建了一個(gè)完全獨(dú)立的新對(duì)象,該對(duì)象與原始對(duì)象沒(méi)有任何關(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)部的引用類(lèi)型屬性進(jìn)行遞歸復(fù)制。常見(jiàn)的實(shí)現(xiàn)深拷貝的方式包括:
- 通過(guò)序列化和反序列化:將對(duì)象序列化為字節(jié)流,然后再反序列化為新的對(duì)象,這樣可以創(chuàng)建一個(gè)與原始對(duì)象完全獨(dú)立的副本。
- 通過(guò)逐個(gè)復(fù)制引用類(lèi)型屬性:對(duì)于每個(gè)引用類(lèi)型的屬性,創(chuàng)建一個(gè)新的實(shí)例并將原始對(duì)象屬性的內(nèi)容復(fù)制到新的實(shí)例中。
需要注意的是,并非所有對(duì)象都能進(jìn)行深拷貝。某些對(duì)象或者類(lèi)中的屬性可能是不可變的,無(wú)需拷貝;某些對(duì)象可能包含循環(huán)引用,無(wú)法完全復(fù)制。因此,在進(jìn)行克隆操作時(shí),需要根據(jù)具體情況選擇合適的拷貝方式。
深拷貝和淺拷貝的主要區(qū)別在于對(duì)于對(duì)象內(nèi)部引用類(lèi)型屬性的處理方式。
數(shù)據(jù)復(fù)制層次的深度:
- 淺拷貝只復(fù)制對(duì)象本身以及對(duì)象內(nèi)部的基本數(shù)據(jù)類(lèi)型的屬性,不會(huì)遞歸地復(fù)制引用類(lèi)型的屬性。因此,在淺拷貝中,新對(duì)象和原始對(duì)象共享對(duì)同一引用類(lèi)型屬性的訪問(wèn)。
- 深拷貝除了復(fù)制對(duì)象本身和基本數(shù)據(jù)類(lèi)型的屬性外,還會(huì)遞歸地復(fù)制對(duì)象內(nèi)部的引用類(lèi)型的屬性。這樣,深拷貝創(chuàng)建了一個(gè)完全獨(dú)立的新對(duì)象,與原始對(duì)象沒(méi)有任何關(guān)聯(lián)。
對(duì)象之間的關(guān)聯(lián)性:
- 淺拷貝得到的新對(duì)象與原始對(duì)象共享對(duì)同一引用類(lèi)型屬性的訪問(wèn)。如果對(duì)其中一個(gè)對(duì)象的引用類(lèi)型屬性進(jìn)行修改,另一個(gè)對(duì)象也會(huì)受到影響。
- 深拷貝得到的新對(duì)象與原始對(duì)象沒(méi)有任何關(guān)聯(lián),修改其中一個(gè)對(duì)象的引用類(lèi)型屬性不會(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 接口和重寫(xiě) clone() 方法:
- Java 中的 Cloneable 接口是一個(gè)標(biāo)記接口,沒(méi)有定義任何方法。通過(guò)實(shí)現(xiàn) Cloneable 接口并重寫(xiě) clone() 方法,可以實(shí)現(xiàn)對(duì)象的淺拷貝。
- 在 clone() 方法中,調(diào)用父類(lèi)的 clone() 方法,并將其返回值進(jìn)行類(lèi)型轉(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 類(lèi),并實(shí)現(xiàn)了 Cloneable 接口。在 clone() 方法中直接調(diào)用了父類(lèi)的 clone() 方法,并進(jìn)行了類(lèi)型轉(zhuǎn)換。通過(guò)調(diào)用 clone() 方法,可以得到一個(gè)新的對(duì)象 person2,它與原始對(duì)象 person1 具有相同的屬性值。當(dāng)修改 person2 的屬性時(shí),不會(huì)影響到 person1。
深拷貝示例
使用序列化和反序列化:
- 將對(duì)象寫(xiě)入到字節(jié)流中,然后再?gòu)淖止?jié)流中讀取出來(lái),這個(gè)過(guò)程會(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 類(lèi)和一個(gè) Person 類(lèi),它們都實(shí)現(xiàn)了 Serializable 接口。通過(guò)序列化和反序列化操作,我們可以實(shí)現(xiàn)深拷貝。在 deepCopy() 方法中,我們使用字節(jié)流將對(duì)象寫(xiě)入到內(nèi)存中,并從內(nèi)存中讀取出來(lái),從而得到一個(gè)新的獨(dú)立對(duì)象。通過(guò)調(diào)用 deepCopy() 方法,可以得到一個(gè)新的對(duì)象 person2,它與原始對(duì)象 person1 完全獨(dú)立。在修改 person2 的屬性時(shí),不會(huì)影響到 person1。 值得注意的是,要實(shí)現(xiàn)深拷貝,所有相關(guān)的類(lèi)都需要實(shí)現(xiàn) Serializable 接口。
深拷貝和淺拷貝的區(qū)別
深拷貝(Deep Copy):
適用場(chǎng)景:
- 當(dāng)源對(duì)象包含引用類(lèi)型的屬性時(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ù)來(lái)復(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ù)類(lèi)型或者不可變對(duì)象,并且不需要復(fù)制引用類(lèi)型的屬性時(shí),可以使用淺拷貝。
- 當(dāng)希望修改副本對(duì)象的屬性同時(shí)影響原始對(duì)象時(shí),可以使用淺拷貝。
工作原理:
- 淺拷貝只復(fù)制對(duì)象及其引用,而不復(fù)制引用指向的實(shí)際對(duì)象,新舊對(duì)象將共享同一個(gè)引用對(duì)象。修改副本對(duì)象會(huì)影響原始對(duì)象。
實(shí)現(xiàn)方式:
- 通常使用對(duì)象的
clone()方法來(lái)進(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)文章
Ribbon單獨(dú)使用,配置自動(dòng)重試,實(shí)現(xiàn)負(fù)載均衡和高可用方式
這篇文章主要介紹了Ribbon單獨(dú)使用,配置自動(dòng)重試,實(shí)現(xiàn)負(fù)載均衡和高可用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
用3個(gè)實(shí)例從原理到實(shí)戰(zhàn)講清楚Log4j史詩(shī)級(jí)漏洞
最近應(yīng)該很多人都在關(guān)注著一個(gè)漏洞Apache Log4j 2遠(yuǎn)程代碼執(zhí)行,該漏洞一旦被攻擊者利用會(huì)造成嚴(yán)重危害,這篇文章主要給大家介紹了關(guān)于如何用3個(gè)實(shí)例從原理到實(shí)戰(zhàn)講清楚Log4j史詩(shī)級(jí)漏洞的相關(guān)資料,需要的朋友可以參考下2021-12-12
詳談Enumeration接口和Iterator接口的區(qū)別
下面小編就為大家?guī)?lái)一篇詳談Enumeration接口和Iterator接口的區(qū)別。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
java正則替換括號(hào)中的逗號(hào)實(shí)現(xiàn)示例
本文主要介紹了java正則替換括號(hào)中的逗號(hào)實(shí)現(xiàn)示例,主要介紹了兩種示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01
Spring?Boot?實(shí)現(xiàn)?IP?限流(保障系統(tǒng)穩(wěn)定性的關(guān)鍵技術(shù))
在Spring?Boot中實(shí)現(xiàn)IP限流是一種簡(jiǎn)單而有效的方式來(lái)保障系統(tǒng)的穩(wěn)定性和可用性,本文通過(guò)實(shí)例代碼講解Spring?Boot實(shí)現(xiàn)IP限流的相關(guān)操作,感興趣的朋友一起看看吧2025-06-06
一文詳解SpringBoot使用Kafka如何保證消息不丟失
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用Kafka如何保證消息不丟失的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下2025-01-01
Java實(shí)現(xiàn)5種限流算法及7種限流方式
本文主要介紹了Java實(shí)現(xiàn)5種限流算法及7種限流方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Springboot實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)管理的示例代碼
最近在做spring boot項(xiàng)目開(kāi)發(fā)中,由于使用@EnableScheduling注解和@Scheduled注解來(lái)實(shí)現(xiàn)的定時(shí)任務(wù),只能靜態(tài)的創(chuàng)建定時(shí)任務(wù),不能動(dòng)態(tài)修改、添加、刪除、啟/停任務(wù),下面通過(guò)本文給大家介紹Springboot實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)管理的方法,感興趣的朋友跟隨小編一起看看吧2023-07-07

