1. 程式人生 > 其它 >面試官問:什麼是淺拷貝和深拷貝?

面試官問:什麼是淺拷貝和深拷貝?

前言

平時我們從資料庫查詢出 po 物件,要返回給前端時,會有另一個物件 vo,此時我們需要將 po 的值複製給 vo,如果是你,你會怎麼做呢?

有時我們除了複製之外,還要求 po 引數值的改變不能影響到 vo,也就是 po 和 vo 是兩個獨立的個體,此時我們又需要怎麼做呢?

帶著這些疑問,我們一起來看下今天所要講解的關於物件複製的知識點。

一、什麼是淺拷貝和深拷貝

淺拷貝

  • 對於基本資料型別的成員變數,淺拷貝直接進行值傳遞,也就是將屬性值複製了一份給新的成員變數

  • 對於引用資料型別的成員變數,比如成員變數是陣列、某個類的物件等,淺拷貝就是引用的傳遞,也就是將成員變數的引用(記憶體地址)複製了一份給新的成員變數,他們指向的是同一個事例。在一個物件修改成員變數的值,會影響到另一個物件中成員變數的值。

深拷貝

  • 對於基本資料型別,深拷貝複製所有基本資料型別的成員變數的值

  • 對於引用資料型別的成員變數,深拷貝申請新的儲存空間,並複製該引用物件所引用的物件,也就是將整個物件複製下來。所以在一個物件修改成員變數的值,不會影響到另一個物件成員變數的值。

二、淺拷貝

1、clone

  • 實現 Cloneable

  • 重寫 clone()方法,並宣告為 public

  • 呼叫 super.clone()

copydemo

@Data
publicclassCopyDemoimplementsCloneable{
privateintage;
privateUseruser;

@Override
publicCopyDemoclone(){
try{
CopyDemoclone=(CopyDemo)super.clone();
returnclone;
}catch(CloneNotSupportedExceptione){
thrownewAssertionError();
}
}
}

user

@Data
publicclassUser{
privateStringname;
}

使用

@Service
publicclassCopyServiceDemo{

publicstaticvoidmain(String[]args){
CopyDemosource=newCopyDemo();
source.setAge(10);

Useruser=newUser();
user.setName("user-舊名字");
source.setUser(user);

CopyDemotarget=source.clone();
System.out.println("改變之前");
System.out.println("source:"+source.getAge());
System.out.println("target:"+target.getAge());
System.out.println("source-user:"+source.getUser().getName());
System.out.println("target-user:"+target.getUser().getName());

source.setAge(20);
user.setName("user-新名字");

System.out.println("改變之後");
System.out.println("source:"+source.getAge());
System.out.println("target:"+target.getAge());
System.out.println("source-user:"+source.getUser().getName());
System.out.println("target-user:"+target.getUser().getName());

}
}

結果

改變之前
source:10
target:10
source-user:user-舊名字
target-user:user-舊名字
改變之後
source:20
target:10
source-user:user-新名字
target-user:user-新名字

從以上結果可以看出

  • 修改 source 的 age,並不會影響到拷貝之後的 target 的 age

  • 修改 source 的 user 的 name,會影響到拷貝之後的 targe 的 user 的 name,因為 target 的 user 跟 source 的 user 所指向的是同一個 user 例項。

2、Apache BeanUtils(不推薦)

Apache BeanUtils 屬於比較古老的工具類,由於存在效能問題,阿里巴巴手冊明確禁止使用該工具類

效能差的原因是:力求做得完美, 在程式碼中增加了非常多的校驗、相容、日誌列印等程式碼,過度的包裝導致效能下降嚴重。

3、Spring BeanUtils

Spring BeanUtils 和上面所提到的 apche 的很像,但是在效率上比 apache 的更高

Spring BeanUtils 的 copyProperties() 方法,第一個是源物件,第二個是目標物件。和 Apache BeanUtils 正好相反,要注意避免踩坑。

importorg.springframework.beans.BeanUtils;

CopyDemotarget=newCopyDemo();
BeanUtils.copyProperties(source,target);

4、Spring BeanCopier

Spring 還為我們提供了一種基於 Cglib 的淺拷貝方式 BeanCopier,引入 spring-core 依賴包後即可使用,它被認為是取代 BeanUtils 的存在。

以下是自己封裝的工具類:

importorg.springframework.cglib.beans.BeanCopier;

publicstatic<T>TcopyByClass(Objectsrc,Class<T>clazz){
BeanCopiercopier=BeanCopier.create(src.getClass(),clazz,false);
Tto=newInstance(clazz);
copier.copy(src,to,null);
returnto;
}

publicstatic<T>TnewInstance(Class<?>clazz){
try{
return(T)clazz.newInstance();
}catch(InstantiationExceptione){
thrownewRuntimeException(e);
}catch(IllegalAccessExceptione){
thrownewRuntimeException(e);
}
}

publicstaticvoidcopyByObj(Objectsrc,Objectdist){
BeanCopiercopier=BeanCopier
.create(src.getClass(),dist.getClass(),false);
copier.copy(src,dist,null);
}

使用

CopyDemotarget=copyByClass(source,CopyDemo.class);

三、深拷貝

1、構造方法-new

手動 new 新的物件,一個屬性一個屬性的 set 過去,屬性多的話,這樣非常麻煩

publicstaticvoidmain(String[]args){
CopyDemosource=newCopyDemo();
source.setAge(10);

Useruser=newUser();
user.setName("user-舊名字");
source.setUser(user);

CopyDemotarget=newCopyDemo();
target.setAge(source.getAge());
UsertargetUser=newUser();
targetUser.setName(source.getUser().getName());
target.setUser(targetUser);

System.out.println("改變之前");
System.out.println("source:"+source.getAge());
System.out.println("target:"+target.getAge());
System.out.println("source-user:"+source.getUser().getName());
System.out.println("target-user:"+target.getUser().getName());

source.setAge(20);
user.setName("user-新名字");

System.out.println("改變之後");
System.out.println("source:"+source.getAge());
System.out.println("target:"+target.getAge());
System.out.println("source-user:"+source.getUser().getName());
System.out.println("target-user:"+target.getUser().getName());

}

改變之前
source:10
target:10
source-user:user-舊名字
target-user:user-舊名字
改變之後
source:20
target:10
source-user:user-新名字
target-user:user-舊名字

Processfinishedwithexitcode0

2、過載 clone()方法

  • 拷貝的物件中還包含其他物件的話,包含的物件也需要重寫 clone 方法

  • super.clone()其實是淺拷貝,所以在重寫 CopyDemo 類的 clone()方法時,user 物件需要呼叫 user.clone()重新賦值

CopyDemo

@Data
publicclassCopyDemoimplementsCloneable{
privateintage;
privateUseruser;

@Override
publicCopyDemoclone(){
try{
CopyDemocopyDemo=(CopyDemo)super.clone();
copyDemo.setUser(this.user.clone());

returncopyDemo;
}catch(CloneNotSupportedExceptione){
thrownewAssertionError();
}
}
}

User

@Data
publicclassUserimplementsCloneable{
privateStringname;

@Override
publicUserclone(){
try{
Userclone=(User)super.clone();
returnclone;
}catch(CloneNotSupportedExceptione){
thrownewAssertionError();
}
}
}

使用

CopyDemotarget=source.clone();

3、Apache Commons Lang 序列化方式

Java 提供了序列化的能力,我們可以先將源物件進行序列化,再反序列化生成拷貝物件。但是,使用序列化的前提是拷貝的類(包括其成員變數)需要實現 Serializable 介面。Apache Commons Lang 包對 Java 序列化進行了封裝:SerializationUtils,我們可以直接使用它。

@Data
publicclassCopyDemoimplementsSerializable{
privatestaticfinallongserialVersionUID=-9820808986091860L;
privateintage;
privateUseruser;
}

@Data
publicclassUserimplementsSerializable{
privatestaticfinallongserialVersionUID=1900781036567192607L;
privateStringname;

}

使用

importorg.apache.commons.lang3.SerializationUtils;

CopyDemotarget=SerializationUtils.clone(source);

4、json 轉化方式

利用 json 將物件轉為 json,在將 json 轉為物件,本質上是反射

//物件
StringjsonString=JSON.toJSONString(source);
CopyDemotarget=JSON.parseObject(jsonString,CopyDemo.class);

//集合
List<CopyDemo>sourceList=Lists.newArrayList();
StringjsonString=JSON.toJSONString(sourceList);
List<CopyDemo>targetList=JSON.parseArray(json,CopyDemo.class);

5、Orika

orika 是深拷貝,但是遇到多層簽到陣列,clone 會有問題,謹慎使用

四、總結

如果物件中只有基本資料型別或者引用資料型別不會改動,則可以使用淺拷貝

如果存在引用資料型別且會改動,則可以使用深拷貝

具體使用拷貝中的哪個方法,需要具體情況具體分析,比如效能考慮、便捷考慮、依賴引入的考慮等等。

今天只是列出了一些常用的方法,還有其他的拷貝方法,可以自行搜尋,多學習,多實踐。


我是臻大蝦,你的支援是對我不斷創作的極大鼓勵,咱們下期見。

關注公眾號:臻大蝦,分享java後端技術乾貨,每天進步一點點

往期推薦

如何更優雅的寫if-else,寫出高質量程式碼

靈魂拷問:java物件轉字串,你真的用對方法了嗎?

idea2021通用配置(史上最全,沒有之一)

git提交也有規範,看下大神是怎麼做的

史上最全idea外掛推薦,你值得擁有

玩轉java8 Stream,老手們都在用

從windows換到mac,idea這些常用的快捷鍵值得收藏

學會這些mysql優化技巧,你離大神又近了一步