一.淺拷貝(shallow copy)
?
?
1.如何實現淺拷貝?
Object類 是所有類的直接或間接父類,Object中存在clone方法,如下
protected native Object clone() throws CloneNotSupportedException;
如果想要使一個類的對象能夠調用clone方法 ,則需要實現Cloneable接口, 并重寫 clone方法:
public class Student implements Cloneable{private int sno ;private String name;//getter ,setter 省略 @Overridepublic Object clone() throws CloneNotSupportedException {Student s = null;try{s = (Student)super.clone();}catch (Exception e){e.printStackTrace();}return s;}}
?
現在測試clone方法:
@Testpublic void test04() throws CloneNotSupportedException {//創建Student對象Student s1 = new Student();s1.setSno(1);s1.setName("Rye");//通過clone 拷貝一個對象Student s2 = (Student)s1.clone();System.out.println("s1:"+s1);System.out.println("s2:"+s2);System.out.println("s1 == s2 ? ==> "+(s1 == s2));}
按照預期,克隆出的對象s2中的字段值應該與s1相同,但與s1對應的對象不在同一塊內存空間,結果如下:


s1:Student{sno=1, name='Rye'} s2:Student{sno=1, name='Rye'} s1 == s2 ? ==> false
此時如果修改 s1中的sno為2,那么會不會影響到s2中的sno呢?
//修改s1中的sno s1.setSno(2);
結果如下:


s1:Student{sno=2, name='Rye'}
s2:Student{sno=1, name='Rye'}
?
此時看似已經完成了 copy, s1 與 s2有著自己不同的值,但如果為Student中新增了Teacher類型的成員變量,結果還是跟上面一樣嗎?讓我們改造下代碼:
public class Teacher {private int tno;private String name;//getter setter省略... }
public class Student implements Cloneable{private int sno ;private String name;private Teacher teacher;//getter ,setter ,toString 省略... @Overridepublic Object clone() throws CloneNotSupportedException {Student s = null;try{s = (Student)super.clone();}catch (Exception e){e.printStackTrace();}return s;} }
?
此時測試代碼如下:
@Testpublic void test02() throws CloneNotSupportedException {Student student1 = new Student();student1.setSno(1);student1.setName("Rye");Teacher teacher = new Teacher();teacher.setTno(1);teacher.setName("LinTong");student1.setTeacher(teacher);Student student2 = (Student)student1.clone();System.out.println("student1:"+student1);System.out.println("student2:"+student2);System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));}
?
運行結果如下:


student1:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}} student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}} student1 == student2 ? ==> false student1.teacher == student2.teacher ? ==> true
?
由此可見,此時經過clone生成的student2, 與 student1.二者中的teacher字段, 指向同一塊內存空間;
那么可能會有人問,這會有什么影響嗎??
我們拷貝的目的,更多的時候是希望獲得全新并且值相同的對象,操作原始對象或拷貝的新對象,對彼此之間互不影響;
此時我們修改student1中teacher的tno ,如下:
//修改teacher中的 tno值為2 student1.getTeacher().setTno(2);
再次運行test:


student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}} student2:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}} student1 == student2 ? ==> false student1.teacher == student2.teacher ? ==> true
?
此時發現,student2中的teacher的tno ,也跟著變化了.
變化的原因是:通過student1執行clone時,基本類型會完全copy一份到student2對應對象內存空間中, 但是對于Teacher對象僅僅是copy了一份Teacher的引用而已.
而student1 與 student2的引用 指向的是同一塊堆內存,因此不論是通過student1或是student2修改teacher 都會影響另外一個;
通過圖會更直觀一些:
?
?
2.淺拷貝中引用類型的變量拷貝的是對象的引用 , 可通過如下思路解決:
Teacher類中也覆寫clone方法:
@Overrideprotected Object clone() {Teacher teacher = null;try {teacher = (Teacher)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return teacher;}
?
修改Student中的clone方法,如下:
@Overridepublic Object clone() {Student s = null;try{s = (Student)super.clone();Teacher t = (Teacher)this.teacher.clone();s.setTeacher(t);}catch (Exception e){e.printStackTrace();}return s;}
?
此時再次運行test:


student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}} student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}} student1 == student2 ? ==> false student1.teacher == student2.teacher ? ==> false
?
由此可見,在copy Student的同時 將Teacher也進行了修改,如圖:
目前來看是滿足了我們的需求,但是如果Teacher類中,同樣也有別的引用類型 的成員變量呢?
那么就同樣需要一直覆寫clone方法,如果這個關系不是特多還可以接受,如果引用關系很復雜就會顯得代碼繁瑣;
此時應該使用序列化完成深度拷貝;
?
?
二.深拷貝(deep copy)
?
使用序列化完成深拷貝
深拷貝是利用對象流,將對象序列化,再反序列化得出新的對象. 因此首先需要實現序列化接口,如下:
public class Student implements Serializable{private static final long serialVersionUID = -2232725257771333130L;private int sno ;private String name;private Teacher teacher;
//getter ,setter,toString()省略... }
Teacher也要實現序列化接口:
public class Teacher implements Serializable{private static final long serialVersionUID = 4477679176385287943L;private int tno;private String name;
//getter ,setter,toString()省略...
}
?
工具方法:
//工具方法public Object cloneObject(Object object) throws IOException, ClassNotFoundException {//將對象序列化ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);objectOutputStream.writeObject(object);//將字節反序列化ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);Object obj = objectInputStream.readObject();return obj;}
?
測試類:
public void test05() throws IOException, ClassNotFoundException {Student student1 = new Student();student1.setSno(1);student1.setName("Rye");Teacher teacher = new Teacher();teacher.setTno(1);teacher.setName("LinTong");student1.setTeacher(teacher);Student student2 = (Student)cloneObject(student1);//修改teacher中的 tno值為2student1.getTeacher().setTno(2);System.out.println("student1:"+student1);System.out.println("student2:"+student2);System.out.println("student1 == student2 ? ==> "+ (student1 ==student2));System.out.println("student1.teacher == student2.teacher ? ==> "+ (student1.getTeacher() ==student2.getTeacher()));}
?
如果Teacher類或者Student類沒有實現序列化接口,則執行時會報異常,如下:
java.io.NotSerializableException: com.example.test.Teacher
?
在都實現了Serializable接口的情況下,運行結果如下:


student1:Student{sno=1, name='Rye', teacher=Teacher{tno=2, name='LinTong'}} student2:Student{sno=1, name='Rye', teacher=Teacher{tno=1, name='LinTong'}} student1 == student2 ? ==> false student1.teacher == student2.teacher ? ==> false
?
由此通過對象流的方式,成功完成了深度拷貝;
?
?
三.重寫clone方法 與 通過序列化 兩種拷貝方式比較:
clone方法:
優點:速度快,效率高
缺點:在對象引用比較深時,使用此方式比較繁瑣
?
通過序列化:
優點:非常簡便的就可以完成深度copy
缺點:由于序列化的過程需要跟磁盤打交道,因此效率會低于clone方式
?
如何抉擇?
實際開發中,根據兩種方式的優缺點進行選擇即可!
?