Java中的clone,sallow?copy和deep?copy的區(qū)別解析
克隆的概念和作用
- 方法需要 return 引用類型,但又不希望自己本身 持有引用類型的對象被修改。
- 程序之間方法的調(diào)用時參數(shù)的傳遞。有些場景為了保證引用類型的參數(shù)不被其他方法修改,可以使用克隆后的值作為參數(shù)傳遞。
sallow copy和deep copy的區(qū)別
sallow copy(淺克隆)
- 復(fù)制基本類型的屬性;
- 引用類型的屬性復(fù)制,
- 復(fù)制棧中的變量 和 變量指向堆內(nèi)存中的對象的指針,
- 不復(fù)制堆內(nèi)存中的對象。
兩個對象使用的引用類型的屬性會互相影響

deep copy(深克?。?/h3>
- 復(fù)制基本類型的屬性;
- 引用類型的屬性復(fù)制,
- 復(fù)制棧中的變量;
- 賦值變量指向堆內(nèi)存中的對象的指針和堆內(nèi)存中的對象。
- 等同于重新創(chuàng)建一個一模一樣的對象,并復(fù)制里面的屬性(數(shù)據(jù)內(nèi)容);
- 測試的時候可以結(jié)合
==和equal進行學(xué)習(xí),== 比較的是基本數(shù)據(jù)類型,- A1=A1:true,0x006E != x006ER:false
- A1.equals(A1):true,0x006E .equals(x006ER):true
- 復(fù)制棧中的變量;
- 賦值變量指向堆內(nèi)存中的對象的指針和堆內(nèi)存中的對象。
==和equal進行學(xué)習(xí),== 比較的是基本數(shù)據(jù)類型,- A1=A1:true,0x006E != x006ER:false
- A1.equals(A1):true,0x006E .equals(x006ER):true

如何實現(xiàn)deep copy
- 實現(xiàn)
Cloneable接口,重寫clone()方法。 - 不實現(xiàn)
Cloneable接口,會報CloneNotSupportedException異常。 Object的clone()方法是淺拷貝,即如果類中屬性有自定義引用類型,只拷貝引用,不拷貝引用指向的對象。
方法1、對象的屬性的Class 也實現(xiàn) Cloneable 接口,克隆對象的同時也克隆屬性。
@Override
public Object clone() throws CloneNotSupportedException {
DPerson p = (DPerson)super.clone();
p.setFood((DFood)p.getFood().clone());
return p;
}
如下:
- DPerson 類實現(xiàn) Cloneable接口,重寫clone()方法,將對象 和屬性全部clone;
- DPerson 引用的對象Food類 實現(xiàn)Cloneable接口,重寫clone()方法;
- 在主程序中調(diào)用DPerson 中的clone()方法,更改克隆后的對象內(nèi)的屬性進行測試
/**
* 測試克隆
*/
public class TestManalDeepClone {
public static void main(String[] args) throws Exception {
DPerson p1 = new DPerson(1, "AKA000", new DFood("米飯"));//創(chuàng)建Person 對象 p1
DPerson p2 = (DPerson)p1.clone();//克隆p1
p2.setName("AKA121");//修改p2的name屬性
p2.getFood().setName("面條");//修改p2的自定義引用類型 food 屬性
System.out.println(p1);//修改p2的自定義引用類型 food 屬性被改變,p1的自定義引用類型 food 屬性也隨之改變,說明p2的food屬性,只拷貝了引用,沒有拷貝food對象
System.out.println(p2);
}
}
class DPerson implements Cloneable {
private int pid;
private String name;
private DFood food;
public DPerson(int pid, String name, DFood food) {
this.pid = pid;
this.name = name;
this.food = food;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
DPerson p = (DPerson)super.clone();
p.setFood((DFood)p.getFood().clone());
return p;
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
}
public DFood getFood() {
return food;
}
public void setFood(DFood food) {
this.food = food;
}
}
class DFood implements Cloneable{
private String name;
public DFood(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}方法2、結(jié)合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷貝
手寫一個方法 cloneBySerizalizable(),使用 ByteStream流和ObjectSteam流實現(xiàn);
/**
* 通過序列化完成克隆
* @return
*/
public Object cloneBySerializable() {
Object obj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return obj;
}如下:
- 整體思想:將數(shù)據(jù)序列化成為字節(jié)流,再反序列化為數(shù)據(jù)行程對象的clone,做出一個全新的、與原始對象完全獨立的對象。;
- 編寫
cloneBySerializable()方法,創(chuàng)建一個Object對象,設(shè)置為成員變量初始化為null,做傳輸使用; - 創(chuàng)建
ByteArrayOutputStream容器,用于接收和存儲后續(xù)寫入的字節(jié)數(shù)據(jù) - 創(chuàng)建一個對象輸出流
ObjectOutputStream oos = new ObjectOutputStream(baos);,包裝在ByteArrayOutputStream之上,不能獨立存在,必須依賴一個底層的輸出流,指定數(shù)據(jù)最終的去向。 - 使用
writeObject方法,將當(dāng)前對象 (this) 序列化為字節(jié)流。這些字節(jié)數(shù)據(jù)被寫入到ByteArrayOutputStream這個內(nèi)存容器中。 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());將對象反序列化會bais中,在通過ObjectInputStream進行讀取,ObjectInputStream必須包裝在ByteArrayInputStream之中;- 通過
readObject方法,從字節(jié)流中讀取數(shù)據(jù)并反序列化,最終創(chuàng)建出一個全新的對象。 - 在主程序中調(diào)用DPerson 中的
cloneBySerializable()方法,更改克隆后的對象內(nèi)的屬性進行測試
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class TestSeriazableClone {
public static void main(String[] args) {
SPerson p1 = new SPerson(1, "AKA000", new SFood("米飯"));//創(chuàng)建 SPerson 對象 p1
SPerson p2 = (SPerson)p1.cloneBySerializable();//克隆 p1
p2.setName("AKA001");//修改 p2 的 name 屬性
p2.getFood().setName("面條");//修改 p2 的自定義引用類型 food 屬性
System.out.println(p1);//修改 p2 的自定義引用類型 food 屬性被改變,p1的自定義引用類型 food 屬性未隨之改變,說明p2的food屬性,只拷貝了引用和 food 對象
System.out.println(p2);
}
}
class SPerson implements Cloneable, Serializable {
private static final long serialVersionUID = -7710144514831611031L;
private int pid;
private String name;
private SFood food;
public SPerson(int pid, String name, SFood food) {
this.pid = pid;
this.name = name;
this.food = food;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 通過序列化完成克隆
* @return
*/
public Object cloneBySerializable() {
Object obj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return obj;
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
}
public SFood getFood() {
return food;
}
public void setFood(SFood food) {
this.food = food;
}
}
class SFood implements Serializable {
private static final long serialVersionUID = -3443815804346831432L;
private String name;
public SFood(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}到此這篇關(guān)于Java中的clone,sallow copy和deep copy的區(qū)別解析的文章就介紹到這了,更多相關(guān)java clone,sallow copy和deep copy區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring-boot-maven-plugin引入出現(xiàn)爆紅(已解決)
這篇文章主要介紹了spring-boot-maven-plugin引入出現(xiàn)爆紅(已解決),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
java實現(xiàn)將結(jié)果集封裝到List中的方法
這篇文章主要介紹了java實現(xiàn)將結(jié)果集封裝到List中的方法,涉及java數(shù)據(jù)庫查詢及結(jié)果集轉(zhuǎn)換的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2016-07-07
Spring Boot集成BCryptPasswordEncoder實現(xiàn)密碼加密與驗證的實現(xiàn)方案
文章介紹了如何在SpringBoot中集成BCryptPasswordEncoder實現(xiàn)密碼的加密和驗證,包括添加依賴、配置BCryptPasswordEncoder、在用戶注冊和登錄時使用BCryptPasswordEncoder進行密碼處理,以及注意事項,感興趣的朋友跟隨小編一起看看吧2025-11-11
SpringBoot實現(xiàn)短信驗證碼校驗方法思路詳解
最近做項目遇到這樣的需求,前端是基于BootStrap,html代碼中有BootStrap樣式實現(xiàn)的,具體后臺實現(xiàn)代碼大家通過本文一起學(xué)習(xí)吧2017-08-08
Spring如何通過@Lazy注解解決構(gòu)造方法循環(huán)依賴問題
循環(huán)依賴其實就是循環(huán)引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環(huán),這篇文章主要給大家介紹了關(guān)于Spring如何通過@Lazy注解解決構(gòu)造方法循環(huán)依賴問題的相關(guān)資料,需要的朋友可以參考下2023-03-03
Java 前臺加后臺精品圖書管理系統(tǒng)的實現(xiàn)
相信每一個學(xué)生學(xué)編程的時候,應(yīng)該都會寫一個小項目——圖書管理系統(tǒng)。為什么這么說呢?我認為一個學(xué)校的氛圍很大一部分可以從圖書館的氛圍看出來,而圖書管理系統(tǒng)這個不大不小的項目,接觸的多,也比較熟悉,不會有陌生感,能夠練手,又有些難度,所以我的小項目也來了2021-11-11
SpringBoot 異常處理/自定義格式校驗的問題實例詳解
文章探討Spring Boot中自定義注解校驗問題,區(qū)分參數(shù)級與類級約束觸發(fā)的異常類型,建議通過@RestControllerAdvice統(tǒng)一處理,并說明如何獲取不同錯誤類型(FieldError/ObjectError)的信息差異,感興趣的朋友一起看看吧2025-07-07

