public class Client { public static void main(String[] args) { //寫封郵件 Email email = new Email("請(qǐng)參加會(huì)議","請(qǐng)與今天12:30到二會(huì)議室參加會(huì)議..."); Person person1 = new Person("張三",email); Person person2 = person1.clone(); person2.setName("李四"); Person person3 = person1.clone(); person3.setName("王五"); person1.getEmail().setContent("請(qǐng)與今天12:00到二會(huì)議室參加會(huì)議..."); System.out.println(person1.getName() + "的郵件內(nèi)容是:" + person1.getEmail().getContent()); System.out.println(person2.getName() + "的郵件內(nèi)容是:" + person2.getEmail().getContent()); System.out.println(person3.getName() + "的郵件內(nèi)容是:" + person3.getEmail().getContent()); } } 在這里同樣是使用張三該對(duì)象實(shí)現(xiàn)對(duì)李四、王五拷貝,最后將張三的郵件內(nèi)容改變?yōu)椋赫?qǐng)與今天12:00到二會(huì)議室參加會(huì)議...。但是結(jié)果是:[java] view%20plain copy ![在CODE上查看代碼片]()
張三的郵件內(nèi)容是:請(qǐng)與今天12:00到二會(huì)議室參加會(huì)議... 李四的郵件內(nèi)容是:請(qǐng)與今天12:00到二會(huì)議室參加會(huì)議... 王五的郵件內(nèi)容是:請(qǐng)與今天12:00到二會(huì)議室參加會(huì)議... 這里我們就疑惑了為什么李四和王五的郵件內(nèi)容也發(fā)送了改變呢?讓他們提前30分鐘到人家會(huì)有意見的!
其實(shí)出現(xiàn)問題的關(guān)鍵就在于clone()方法上,我們知道該clone()方法是使用Object類的clone()方法,但是該方法存在一個(gè)缺陷,它并不會(huì)將對(duì)象的所有屬性全部拷貝過來,而是有選擇性的拷貝,基本規(guī)則如下:
1、%20基本類型
如果變量是基本很類型,則拷貝其值,比如int、float等。
2、%20對(duì)象
如果變量是一個(gè)實(shí)例對(duì)象,則拷貝其地址引用,也就是說此時(shí)新對(duì)象與原來對(duì)象是公用該實(shí)例變量。
3、%20String字符串
%20 %20 若變量為String字符串,則拷貝其地址引用。但是在修改時(shí),它會(huì)從字符串池中重新生成一個(gè)新的字符串,原有紫都城對(duì)象保持不變。
基于上面上面的規(guī)則,我們很容易發(fā)現(xiàn)問題的所在,他們?nèi)吖靡粋€(gè)對(duì)象,張三修改了該郵件內(nèi)容,則李四和王五也會(huì)修改,所以才會(huì)出現(xiàn)上面的情況。對(duì)于這種情況我們還是可以解決的,只需要在clone()方法里面新建一個(gè)對(duì)象,然后張三引用該對(duì)象即可:
[java] view%20plain copy ![在CODE上查看代碼片]()
protected 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提供的一種簡(jiǎn)單的拷貝機(jī)制,不便于直接使用。
對(duì)于上面的解決方案還是存在一個(gè)問題,若我們系統(tǒng)中存在大量的對(duì)象是通過拷貝生成的,如果我們每一個(gè)類都寫一個(gè)clone()方法,并將還需要進(jìn)行深拷貝,新建大量的對(duì)象,這個(gè)工程是非常大的,這里我們可以利用序列化來實(shí)現(xiàn)對(duì)象的拷貝。
二、利用序列化實(shí)現(xiàn)對(duì)象的拷貝
如何利用序列化來完成對(duì)象的拷貝呢?在內(nèi)存中通過字節(jié)流的拷貝是比較容易實(shí)現(xiàn)的。把母對(duì)象寫入到一個(gè)字節(jié)流中,再從字節(jié)流中將其讀出來,這樣就可以創(chuàng)建一個(gè)新的對(duì)象了,并且該新對(duì)象與母對(duì)象之間并不存在引用共享的問題,真正實(shí)現(xiàn)對(duì)象的深拷貝。
[java] view%20plain copy ![在CODE上查看代碼片]()
public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj){ T cloneObj = null; try { //寫入字節(jié)流 ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new ObjectOutputStream(out); obs.writeObject(obj); obs.close(); //分配內(nèi)存,寫入原始對(duì)象,生成新對(duì)象 ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(ios); //返回生成的新對(duì)象 cloneObj = (T) ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; } } 使用該工具類的對(duì)象必須要實(shí)現(xiàn)Serializable接口,否則是沒有辦法實(shí)現(xiàn)克隆的。
[html] view%20plain copy ![在CODE上查看代碼片]()
public class Person implements Serializable{ private static final long serialVersionUID = 2631590509760908280L; .................. //去除clone()方法 } public class Email implements Serializable{ private static final long serialVersionUID = 1267293988171991494L; .................... } 所以使用該工具類的對(duì)象只要實(shí)現(xiàn)Serializable接口就可實(shí)現(xiàn)對(duì)象的克隆,無須繼承Cloneable接口實(shí)現(xiàn)clone()方法。
[java] view%20plain copy ![在CODE上查看代碼片]()
public class Client { public static void main(String[] args) { //寫封郵件 Email email = new Email("請(qǐng)參加會(huì)議","請(qǐng)與今天12:30到二會(huì)議室參加會(huì)議..."); Person person1 = new Person("張三",email); Person person2 = CloneUtils.clone(person1); person2.setName("李四"); Person person3 = CloneUtils.clone(person1); person3.setName("王五"); person1.getEmail().setContent("請(qǐng)與今天12:00到二會(huì)議室參加會(huì)議..."); System.out.println(person1.getName() + "的郵件內(nèi)容是:" + person1.getEmail().getContent()); System.out.println(person2.getName() + "的郵件內(nèi)容是:" + person2.getEmail().getContent()); System.out.println(person3.getName() + "的郵件內(nèi)容是:" + person3.getEmail().getContent()); } } ------------------- Output: 張三的郵件內(nèi)容是:請(qǐng)與今天12:00到二會(huì)議室參加會(huì)議... 李四的郵件內(nèi)容是:請(qǐng)與今天12:30到二會(huì)議室參加會(huì)議... 王五的郵件內(nèi)容是:請(qǐng)與今天12:30到二會(huì)議室參加會(huì)議...