一看就懂的,java深拷貝淺拷貝
1、直接賦值
/* 建立類 */ class Resume { private String name; //姓名 private String sex; //性別 private int age; //年齡 private String experience; //工作經歷 public Resume(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void setExperience(String experience) { this.experience = experience; } public String getExperience() { return experience; } public void displayResume() { System.out.println("姓名:"+name+" 性別:"+sex+" 年齡:"+age); System.out.println("工作經歷:"+experience); } } public class MainClass { public static void main(String[] args) { Resume zhangsan = new Resume("zhangsan","男",24); zhangsan.setExperience("2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼復制"); zhangsan.displayResume(); Resume zhangsan1 = zhangsan; zhangsan1.setExperience("2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等"); zhangsan.displayResume(); zhangsan1.displayResume(); } }
程序運行結果
姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼復制 姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等 姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等
在本程序中,生成了一份zhangsan的簡歷。之后又復制了一份簡歷zhangsan1,可見zhangsan1中工作經歷發生變化時,zhangsan的工作經歷也發生了變化。
2、淺拷貝
/* 建立類,實現Clone方法 */ class Resume implements Cloneable{ private String name; //姓名 private String sex; //性別 private int age; //年齡 private String experience; //工作經歷 public Resume(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void setExperience(String experience) { this.experience = experience; } public String getExperience() { return experience; } public void displayResume() { System.out.println("姓名:"+name+" 性別:"+sex+" 年齡:"+age); System.out.println("工作經歷:"+experience); } public Object clone() { try { return (Resume)super.clone(); } catch (Exception e) { e.printStackTrace(); return null; } } } public class MainClass { public static void main(String[] args) { Resume zhangsan = new Resume("zhangsan","男",24); zhangsan.setExperience("2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼拷貝和粘貼"); zhangsan.displayResume(); Resume zhangsan1 = (Resume)zhangsan.clone(); zhangsan1.setAge(23); zhangsan1.displayResume(); Resume zhangsan2 = (Resume)zhangsan.clone(); zhangsan2.setExperience("2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼"); zhangsan2.displayResume(); zhangsan.displayResume(); } }
程序運行結果
姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼拷貝和粘貼 姓名:zhangsan 性別:男 年齡:23 工作經歷:2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼拷貝和粘貼 姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼 姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學,精通JAVA,C,C++,C#等代碼拷貝和粘貼
class Experience { private String educationBackground; private String skills; public void setExperience(String educationBackground, String skills) { // TODO Auto-generated constructor stub this.educationBackground = educationBackground; this.skills = skills; } public String toString() { return educationBackground + skills; } } /* 建立類,實現Clone方法 */ class Resume implements Cloneable{ private String name; //姓名 private String sex; //性別 private int age; //年齡 private Experience experience; //工作經歷 public Resume(String name, String sex, int age) { this.name = name; this.sex = sex; this.age = age; this.experience = new Experience(); } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public Experience getExperience() { return experience; } public void setExperience(String educationBackground, String skills) { experience.setExperience(educationBackground, skills); } public void displayResume() { System.out.println("姓名:"+name+" 性別:"+sex+" 年齡:"+age); System.out.println("工作經歷:"+experience.toString()); } public Object clone() { try { return (Resume)super.clone(); } catch (Exception e) { e.printStackTrace(); return null; } } } public class MainClass { public static void main(String[] args) { Resume zhangsan = new Resume("zhangsan","男",24); zhangsan.setExperience("2009-2013就讀於家里蹲大學","精通JAVA,C,C++,C#等代碼拷貝和粘貼"); zhangsan.displayResume(); Resume zhangsan2 = (Resume)zhangsan.clone(); zhangsan2.setExperience("2009-2013就讀於家里蹲大學","精通JAVA,C,C++,C#等"); zhangsan2.displayResume(); zhangsan.displayResume(); zhangsan2.displayResume(); } }
程序運行結果:
姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學精通JAVA,C,C++,C#等代碼拷貝和粘貼 姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學精通JAVA,C,C++,C#等 姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學精通JAVA,C,C++,C#等 姓名:zhangsan 性別:男 年齡:24 工作經歷:2009-2013就讀於家里蹲大學精通JAVA,C,C++,C#等
我們看一下上面兩段程序差異在哪兒,第一段程序的工作經歷是作為Resume類的一個普通的成員變量,也就是值屬性。而后面一段程序中,工作經歷Experience是一個類。結合上面程序的運行結果,我們再來理解“如果該字段是引用類型的話,則復制引用但不復制引用的對象。因此,原始對象及其副本引用同一個對象。”其實也就是說,zhangsan和zhangsan2里面的Experience類指向的是同一個對象嘛!那不管是zhangsan里面的Experience變化,還是zhangsan2里面的Experience變化都會影響另外一個啊。
3、深拷貝
其實出現問題的關鍵就在於clone()方法上,我們知道該clone()方法是使用Object類的clone()方法,但是該方法存在一個缺陷,它並不會將對象的所有屬性全部拷貝過來,而是有選擇性的拷貝,基本規則如下:
1、 基本類型
如果變量是基本很類型,則拷貝其值,比如int、float等。
2、 對象
如果變量是一個實例對象,則拷貝其地址引用,也就是說此時新對象與原來對象是公用該實例變量。
3、 String字符串
若變量為String字符串,則拷貝其地址引用。但是在修改時,它會從字符串池中重新生成一個新的字符串,原有紫都城對象保持不變。
基於上面上面的規則,我們很容易發現問題的所在,他們三者公用一個對象,張三修改了該郵件內容,則李四和王五也會修改,所以才會出現上面的情況。對於這種情況我們還是可以解決的,只需要在clone()方法里面新建一個對象,然后張三引用該對象即可:
rotected Person clone() { Person person = null; try { person = (Person) super.clone(); person.setEmail(new Email(person.getEmail().getObject(),person.getEmail().getContent())); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return person; }
所以:淺拷貝只是Java提供的一種簡單的拷貝機制,不便於直接使用。
對於上面的解決方案還是存在一個問題,若我們系統中存在大量的對象是通過拷貝生成的,如果我們每一個類都寫一個clone()方法,並將還需要進行深拷貝,新建大量的對象,這個工程是非常大的,這里我們可以利用序列化來實現對象的拷貝。
如何利用序列化來完成對象的拷貝呢?在內存中通過字節流的拷貝是比較容易實現的。把母對象寫入到一個字節流中,再從字節流中將其讀出來,這樣就可以創建一個新的對象了,並且該新對象與母對象之間並不存在引用共享的問題,真正實現對象的深拷貝。
public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj){ T cloneObj = null; try { //寫入字節流 ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new ObjectOutputStream(out); obs.writeObject(obj); obs.close(); //分配內存,寫入原始對象,生成新對象 ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(ios); //返回生成的新對象 cloneObj = (T) ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; } }
使用該工具類的對象必須要實現Serializable接口,否則是沒有辦法實現克隆的。
public class Person implements Serializable{ private static final long serialVersionUID = 2631590509760908280L; .................. //去除clone()方法 } public class Email implements Serializable{ private static final long serialVersionUID = 1267293988171991494L; .................... }
所以使用該工具類的對象只要實現Serializable接口就可實現對象的克隆,無須繼承Cloneable接口實現clone()方法。
public class Client { public static void main(String[] args) { //寫封郵件 Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議..."); Person person1 = new Person("張三",email); Person person2 = CloneUtils.clone(person1); person2.setName("李四"); Person person3 = CloneUtils.clone(person1); person3.setName("王五"); person1.getEmail().setContent("請與今天12:00到二會議室參加會議..."); System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent()); System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent()); System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent()); } } ------------------- Output: 張三的郵件內容是:請與今天12:00到二會議室參加會議... 李四的郵件內容是:請與今天12:30到二會議室參加會議... 王五的郵件內容是:請與今天12:30到二會議室參加會議...
