介紹
開發(fā)過(guò)程中,有時(shí)會(huì)遇到把現(xiàn)有的一個(gè)對(duì)象的所有成員屬性拷貝給另一個(gè)對(duì)象的需求崭倘。
比如說(shuō)對(duì)象 A 和對(duì)象 B祥楣,二者都是 ClassC 的對(duì)象,具有成員變量 a 和 b培廓,現(xiàn)在對(duì)對(duì)象 A 進(jìn)行拷貝賦值給 B惹悄,也就是 B.a = A.a; B.b = A.b;
這時(shí)再去改變 B 的屬性 a 或者 b 時(shí),可能會(huì)遇到問題:假設(shè) a 是基礎(chǔ)數(shù)據(jù)類型肩钠,b 是引用類型泣港。
當(dāng)改變 B.a 的值時(shí),沒有問題价匠;
當(dāng)改變 B.b 的值時(shí)当纱,同時(shí)也會(huì)改變 A.b 的值,因?yàn)槠鋵?shí)上面的例子中只是把 A.b 賦值給了 B.b踩窖,因?yàn)槭?b 引用類型的坡氯,所以它們是指向同一個(gè)地址的。這可能就會(huì)給我們使用埋下隱患洋腮。
Java 中的數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型箫柳。對(duì)于這兩種數(shù)據(jù)類型,在進(jìn)行賦值操作啥供、用作方法參數(shù)或返回值時(shí)悯恍,會(huì)有值傳遞和引用(地址)傳遞的差別。
拷貝分類
上面的問題伙狐,其實(shí)就是因?yàn)閷?duì)拷貝的不熟悉導(dǎo)致的涮毫。
根據(jù)對(duì)對(duì)象屬性的拷貝程度(基本數(shù)據(jù)類和引用類型),會(huì)分為兩種:
- 淺拷貝 (
Shallow Copy
) - 深拷貝 (
Deep Copy
)
淺拷貝
1. 淺拷貝介紹
淺拷貝是按位拷貝對(duì)象鳞骤,它會(huì)創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝黍判。如果屬性是基本類型豫尽,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型)顷帖,拷貝的就是內(nèi)存地址 美旧,因此如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象贬墩。即默認(rèn)拷貝構(gòu)造函數(shù)只是對(duì)對(duì)象進(jìn)行淺拷貝復(fù)制(逐個(gè)成員依次拷貝)榴嗅,即只復(fù)制對(duì)象空間而不復(fù)制資源。
2. 淺拷貝特點(diǎn)
(1) 對(duì)于基本數(shù)據(jù)類型的成員對(duì)象陶舞,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的嗽测,所以是直接將屬性值賦值給新的對(duì)象。基礎(chǔ)類型的拷貝唠粥,其中一個(gè)對(duì)象修改該值疏魏,不會(huì)影響另外一個(gè)。
(2) 對(duì)于引用類型晤愧,比如數(shù)組或者類對(duì)象大莫,因?yàn)橐妙愋褪且脗鬟f,所以淺拷貝只是把內(nèi)存地址賦值給了成員變量官份,它們指向了同一內(nèi)存空間只厘。改變其中一個(gè),會(huì)對(duì)另外一個(gè)也產(chǎn)生影響舅巷。
結(jié)構(gòu)圖如下:
3. 淺拷貝的實(shí)現(xiàn)
實(shí)現(xiàn)對(duì)象拷貝的類羔味,需要實(shí)現(xiàn) Cloneable
接口,并覆寫 clone()
方法悄谐。
示例如下:
public class Subject {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
public class Student implements Cloneable {
//引用類型
private Subject subject;
//基礎(chǔ)數(shù)據(jù)類型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 重寫clone()方法
* @return
*/
@Override
public Object clone() {
//淺拷貝
try {
// 直接調(diào)用父類的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
public class ShallowCopy {
public static void main(String[] args) {
Subject subject = new Subject("yuwen");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("Lynn");
studentA.setAge(20);
Student studentB = (Student) studentA.clone();
studentB.setName("Lily");
studentB.setAge(18);
Subject subjectB = studentB.getSubject();
subjectB.setName("lishi");
System.out.println("studentA:" + studentA.toString());
System.out.println("studentB:" + studentB.toString());
}
}
輸出的結(jié)果:
studentA:[Student: 460141958,subject:[Subject: 1163157884,name:lishi],name:Lynn,age:20]
studentB:[Student: 1956725890,subject:[Subject: 1163157884,name:lishi],name:Lily,age:18]
由輸出的結(jié)果可見介评,通過(guò) studentA.clone()
拷貝對(duì)象后得到的 studentB
,和 studentA
是兩個(gè)不同的對(duì)象爬舰。studentA
和 studentB
的基礎(chǔ)數(shù)據(jù)類型的修改互不影響们陆,而引用類型 subject
修改后是會(huì)有影響的。
淺拷貝和對(duì)象拷貝的區(qū)別:
public static void main(String[] args) {
Subject subject = new Subject("yuwen");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("Lynn");
studentA.setAge(20);
Student studentB = studentA;
studentB.setName("Lily");
studentB.setAge(18);
Subject subjectB = studentB.getSubject();
subjectB.setName("lishi");
System.out.println("studentA:" + studentA.toString());
System.out.println("studentB:" + studentB.toString());
}
這里把 Student studentB = (Student) studentA.clone()
換成了 Student studentB = studentA
情屹。
輸出的結(jié)果:
studentA:[Student: 460141958,subject:[Subject: 1163157884,name:lishi],name:Lily,age:18]
studentB:[Student: 460141958,subject:[Subject: 1163157884,name:lishi],name:Lily,age:18]
可見,對(duì)象拷貝后沒有生成新的對(duì)象垃你,二者的對(duì)象地址是一樣的;而淺拷貝的對(duì)象地址是不一樣的惜颇。
深拷貝
1. 深拷貝介紹
通過(guò)上面的例子可以看到,淺拷貝會(huì)帶來(lái)數(shù)據(jù)安全方面的隱患凌摄,例如我們只是想修改了 studentB
的 subject
,但是 studentA
的 subject
也被修改了锨亏,因?yàn)樗鼈兌际侵赶虻耐粋€(gè)地址。所以器予,此種情況下,我們需要用到深拷貝乾翔。
深拷貝,在拷貝引用類型成員變量時(shí),為引用類型的數(shù)據(jù)成員另辟了一個(gè)獨(dú)立的內(nèi)存空間钧惧,實(shí)現(xiàn)真正內(nèi)容上的拷貝。
2. 深拷貝特點(diǎn)
(1) 對(duì)于基本數(shù)據(jù)類型的成員對(duì)象浓瞪,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的,所以是直接將屬性值賦值給新的對(duì)象乾颁。基礎(chǔ)類型的拷貝英岭,其中一個(gè)對(duì)象修改該值,不會(huì)影響另外一個(gè)(和淺拷貝一樣)诅妹。
(2) 對(duì)于引用類型罚勾,比如數(shù)組或者類對(duì)象,深拷貝會(huì)新建一個(gè)對(duì)象空間吭狡,然后拷貝里面的內(nèi)容尖殃,所以它們指向了不同的內(nèi)存空間。改變其中一個(gè)划煮,不會(huì)對(duì)另外一個(gè)也產(chǎn)生影響送丰。
(3) 對(duì)于有多層對(duì)象的,每個(gè)對(duì)象都需要實(shí)現(xiàn) Cloneable
并重寫 clone()
方法弛秋,進(jìn)而實(shí)現(xiàn)了對(duì)象的串行層層拷貝器躏。
(4) 深拷貝相比于淺拷貝速度較慢并且花銷較大。
結(jié)構(gòu)圖如下:
3. 深拷貝的實(shí)現(xiàn)
對(duì)于 Student
的引用類型的成員變量 Subject
蟹略,需要實(shí)現(xiàn) Cloneable
并重寫 clone()
方法登失。
public class Subject implements Cloneable {
private String name;
public Subject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用類型的成員屬性,也應(yīng)該和 Student 類一樣實(shí)現(xiàn)
return super.clone();
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
在 Student
的 clone()
方法中挖炬,需要拿到拷貝自己后產(chǎn)生的新的對(duì)象揽浙,然后對(duì)新的對(duì)象的引用類型再調(diào)用拷貝操作,實(shí)現(xiàn)對(duì)引用類型成員變量的深拷貝茅茂。
public class Student implements Cloneable {
//引用類型
private Subject subject;
//基礎(chǔ)數(shù)據(jù)類型
private String name;
private int age;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* 重寫clone()方法
* @return
*/
@Override
public Object clone() {
//深拷貝
try {
// 直接調(diào)用父類的clone()方法
Student student = (Student) super.clone();
student.subject = (Subject) subject.clone();
return student;
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
一樣的使用方式
public class ShallowCopy {
public static void main(String[] args) {
Subject subject = new Subject("yuwen");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("Lynn");
studentA.setAge(20);
Student studentB = (Student) studentA.clone();
studentB.setName("Lily");
studentB.setAge(18);
Subject subjectB = studentB.getSubject();
subjectB.setName("lishi");
System.out.println("studentA:" + studentA.toString());
System.out.println("studentB:" + studentB.toString());
}
}
輸出結(jié)果:
studentA:[Student: 460141958,subject:[Subject: 1163157884,name:yuwen],name:Lynn,age:20]
studentB:[Student: 1956725890,subject:[Subject: 356573597,name:lishi],name:Lily,age:18]
由輸出結(jié)果可見捏萍,深拷貝后太抓,不管是基礎(chǔ)數(shù)據(jù)類型還是引用類型的成員變量空闲,修改其值都不會(huì)相互造成影響。
參考: