Java深克隆和淺克隆的原理及實(shí)現(xiàn)
參考:
http://www.reibang.com/p/94dbef2de298
https://www.cnblogs.com/shakinghead/p/7651502.html
Java 中的數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。對于這兩種數(shù)據(jù)類型,在進(jìn)行賦值操作洋措、用作方法參數(shù)或返回值時(shí),會有值傳遞和引用(地址)傳遞的差別。
根據(jù)對對象屬性的拷貝程度(基本數(shù)據(jù)類和引用類型)率挣,會分為兩種:
淺拷貝 (Shallow Copy)
深拷貝 (Deep Copy)
淺拷貝
淺拷貝是按位拷貝對象清寇,它會創(chuàng)建一個(gè)新對象,這個(gè)對象有著原始對象屬性值的一份精確拷貝做葵。如果屬性是基本類型占哟,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型)酿矢,拷貝的就是內(nèi)存地址 榨乎,因此如果其中一個(gè)對象改變了這個(gè)地址,就會影響到另一個(gè)對象瘫筐。即默認(rèn)拷貝構(gòu)造函數(shù)只是對對象進(jìn)行淺拷貝復(fù)制(逐個(gè)成員依次拷貝)蜜暑,即只復(fù)制對象空間而不復(fù)制資源。
(1) 對于基本數(shù)據(jù)類型的成員對象策肝,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的肛捍,所以是直接將屬性值賦值給新的對象≈冢基礎(chǔ)類型的拷貝拙毫,其中一個(gè)對象修改該值,不會影響另外一個(gè)棺禾。
(2) 對于引用類型恬偷,比如數(shù)組或者類對象,因?yàn)橐妙愋褪且脗鬟f帘睦,所以淺拷貝只是把內(nèi)存地址賦值給了成員變量袍患,它們指向了同一內(nèi)存空間。改變其中一個(gè)竣付,會對另外一個(gè)也產(chǎn)生影響诡延。
結(jié)構(gòu)圖如下:
淺拷貝的實(shí)現(xiàn)
實(shí)現(xiàn)對象拷貝的類,需要實(shí)現(xiàn) Cloneable 接口古胆,并覆寫 clone() 方法
示例:
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Teacher implements Cloneable {
private String name;
private int age;
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;
public Student(String name,int age,Teacher teacher){
this.name = name;
this.age = age;
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", teacher=" + teacher +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("王老師",30);
Student student = new Student("張三", 20, teacher);
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));
Student student1 = (Student) student.clone();
student1.setName("李四");
student1.setAge(40);
Teacher teacher1 = student1.getTeacher();
teacher1.setAge(90);
System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
}
}
運(yùn)行結(jié)果:
student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=30}},hashcode1872034366
teacher:Teacher{name='王老師', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老師', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老師', age=90},hashcode:1581781576
student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=90}},hashcode1872034366
由輸出的結(jié)果可見肆良,通過 student.clone() 拷貝對象后得到的 student1筛璧,和 student 是兩個(gè)不同的對象。student 和 student1 的基礎(chǔ)數(shù)據(jù)類型的修改互不影響惹恃,而引用類型 Teacher 修改后是會有影響的夭谤。
student的基礎(chǔ)數(shù)據(jù)類型:name = "張三" ,age = 20
student1的基礎(chǔ)數(shù)據(jù)類型:name = "李四" 巫糙,age = 90
student1的引用類型teacher朗儒,修改age = 90 之后,student 中teacher.age =90 ;
可以通過hascode打印的值看的出來参淹,引用類型的是相同內(nèi)存地址醉锄,所以修改copy后中teacher.age,直接影響原來student中teacher對象的age
深拷貝
通過上面的例子可以看到浙值,淺拷貝會帶來數(shù)據(jù)安全方面的隱患恳不,例如我們只是想修改了 student 的 teacher,但是 student1 的 teacher 也被修改了开呐,因?yàn)樗鼈兌际侵赶虻耐粋€(gè)地址烟勋。所以,此種情況下筐付,我們需要用到深拷貝卵惦。
深拷貝,在拷貝引用類型成員變量時(shí)家妆,為引用類型的數(shù)據(jù)成員另辟了一個(gè)獨(dú)立的內(nèi)存空間,實(shí)現(xiàn)真正內(nèi)容上的拷貝冕茅。
- 深拷貝特點(diǎn)
(1) 對于基本數(shù)據(jù)類型的成員對象伤极,因?yàn)榛A(chǔ)數(shù)據(jù)類型是值傳遞的,所以是直接將屬性值賦值給新的對象姨伤∩谄海基礎(chǔ)類型的拷貝,其中一個(gè)對象修改該值乍楚,不會影響另外一個(gè)(和淺拷貝一樣)当编。
(2) 對于引用類型,比如數(shù)組或者類對象徒溪,深拷貝會新建一個(gè)對象空間忿偷,然后拷貝里面的內(nèi)容,所以它們指向了不同的內(nèi)存空間臊泌。改變其中一個(gè)鲤桥,不會對另外一個(gè)也產(chǎn)生影響。
(3) 對于有多層對象的渠概,每個(gè)對象都需要實(shí)現(xiàn) Cloneable 并重寫 clone() 方法茶凳,進(jìn)而實(shí)現(xiàn)了對象的串行層層拷貝嫂拴。
(4) 深拷貝相比于淺拷貝速度較慢并且花銷較大。
3.深拷貝的實(shí)現(xiàn)方法主要有兩種:
(1)贮喧、通過重寫clone方法來實(shí)現(xiàn)深拷貝
(2)筒狠、通過對象序列化實(shí)現(xiàn)深拷貝
結(jié)構(gòu)圖如下:
一、通過重寫clone方法來實(shí)現(xiàn)深拷貝
示例:
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Teacher implements Cloneable {
private String name;
private int age;
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.lvyuanj.core.model;
import lombok.Data;
@Data
public class Student implements Cloneable{
private String name;
private int age;
private Teacher teacher;
public Student(String name,int age,Teacher teacher){
this.name = name;
this.age = age;
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", teacher=" + teacher +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.teacher = (Teacher) teacher.clone();
return student;
}
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("王老師",30);
Student student = new Student("張三", 20, teacher);
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));
Student student1 = (Student) student.clone();
student1.setName("李四");
student1.setAge(40);
Teacher teacher1 = student1.getTeacher();
teacher1.setAge(90);
System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));
System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));
}
}
運(yùn)行結(jié)果:
student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=30}},hashcode1872034366
teacher:Teacher{name='王老師', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老師', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老師', age=90},hashcode:1670675563
student:Student{name='張三', age=20, teacher=Teacher{name='王老師', age=30}},hashcode1872034366
二箱沦、通過對象序列化實(shí)現(xiàn)深拷貝
雖然層次調(diào)用clone方法可以實(shí)現(xiàn)深拷貝辩恼,但是顯然代碼量實(shí)在太大。特別對于屬性數(shù)量比較多饱普、層次比較深的類而言运挫,每個(gè)類都要重寫clone方法太過繁瑣。將對象序列化為字節(jié)序列后套耕,默認(rèn)會將該對象的整個(gè)對象圖進(jìn)行序列化谁帕,再通過反序列即可完美地實(shí)現(xiàn)深拷貝。
示例:
package com.lvyuanj.core.model;
import lombok.Data;
import java.io.Serializable;
@Data
public class TeacherA implements Serializable {
private String name;
private int sex;
public TeacherA(String name,int sex){
this.name = name;
this.sex = sex;
}
@Override
public String toString() {
return "TeacherA{" +
"name='" + name + '\'' +
", sex=" + sex +
'}';
}
}
package com.lvyuanj.core.model;
import lombok.Data;
import java.io.*;
@Data
public class StudentA implements Serializable {
private String name;
private int sex;
private TeacherA teacherA;
public StudentA(String name,int sex,TeacherA teacherA){
this.name = name;
this.sex = sex;
this.teacherA = teacherA;
}
@Override
public String toString() {
return "StudentA{" +
"name='" + name + '\'' +
", sex=" + sex +
", teacherA=" + teacherA +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
TeacherA teacherA = new TeacherA("Arvin", 0);
StudentA studentA = new StudentA("Tom", 1, teacherA);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(studentA);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
StudentA copystu = (StudentA) ois.readObject();
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
copystu.setName("copy-Arvin");
copystu.getTeacherA().setName("copy-tom");
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
}
}
運(yùn)行結(jié)果:
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:317574433
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:317574433
可以通過很簡潔的代碼即可完美實(shí)現(xiàn)深拷貝冯袍。不過要注意的是匈挖,如果某個(gè)屬性被transient修飾,那么該屬性就無法被拷貝了康愤。
示例:
package com.lvyuanj.core.model;
import lombok.Data;
import java.io.*;
@Data
public class StudentA implements Serializable {
private transient String name;
private int sex;
private TeacherA teacherA;
public StudentA(String name,int sex,TeacherA teacherA){
this.name = name;
this.sex = sex;
this.teacherA = teacherA;
}
@Override
public String toString() {
return "StudentA{" +
"name='" + name + '\'' +
", sex=" + sex +
", teacherA=" + teacherA +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
TeacherA teacherA = new TeacherA("Arvin", 0);
StudentA studentA = new StudentA("Tom", 1, teacherA);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(studentA);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
StudentA copystu = (StudentA) ois.readObject();
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
copystu.setName("copy-Arvin");
copystu.getTeacherA().setName("copy-tom");
System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));
System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
}
}
運(yùn)行結(jié)果:
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:1854731462
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:1854731462
以上代碼中StudentA中name屬性被transient修飾儡循,copy之后name=null, 打印copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462