一.淺拷貝(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 省略 @Override public Object clone() throws CloneNotSupportedException { Student s = null; try{ s = (Student)super.clone(); }catch (Exception e){ e.printStackTrace(); } return s; } }
現在測試clone方法:
@Test public 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 省略... @Override public Object clone() throws CloneNotSupportedException { Student s = null; try{ s = (Student)super.clone(); }catch (Exception e){ e.printStackTrace(); } return s; } }
此時測試代碼如下:
@Test public 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方法:
@Override protected Object clone() { Teacher teacher = null; try { teacher = (Teacher)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return teacher; }
修改Student中的clone方法,如下:
@Override public 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值為2 student1.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方式
如何抉擇?
實際開發中,根據兩種方式的優缺點進行選擇即可!