Java物件克隆——淺克隆和深克隆的區別
在Java中物件的克隆有深克隆和淺克隆之分。有這種區分的原因是Java中分為基本資料型別和引用資料型別,對於不同的資料型別在記憶體中的儲存的區域是不同的。基本資料型別儲存在棧中,引用資料型別儲存在堆中。
什麼是克隆
克隆就是依據已經有的資料,創造一份新的完全一樣的資料拷貝。
實現克隆有多種方式,可以手工的new出一個新的物件,然後將原來的物件資訊一個一個的set到新的物件中。還有就是使用clone方法。使用clone方法必須滿足:
-
實現Cloneable介面
-
使用public訪問修飾符重新定義clone方法。
在不使用克隆方法時,將類A的例項A1直接賦給類A的新例項A2時會出現這樣的情況,修改A2的屬性,A1的屬性也發生了改變。這是因為賦值操作之後,A1和A2指向同一個物件,就像是使用不同的顯示器操作同一個伺服器一樣,兩個顯示器顯示的都是一個伺服器上的內容。
-
淺克隆
對於一個只含有基本資料型別的類來說使用clone方法,是完全沒有問題的,用圖表來表示該情形的clone操作:
Customer customer2=customer1.clone(); |
||
customer1 |
ID |
123 |
age |
23 |
|
customer2 |
ID |
123 |
age |
23 |
|
customer2.setAge(32); |
||
customer1 |
ID |
123 |
age |
23 |
|
customer2 |
ID |
123 |
age |
32 |
在clone後customer1和customer2之間資料互不影響。
但是如果在Customer類中有一個引用型別的屬性Address呢?
public static void main(String[] args) throws CloneNotSupportedException { Address address = new Address("CH" , "SD" , "QD"); Customer customer1 = new Customer(1 , 23 , address); Customer customer2 = customer1.clone(); customer2.getAddress().setCity("JN"); customer2.setID(2); System.out.println("customer1:"+customer1.toString()); System.out.println("customer2:"+customer2.toString()); } } class Customer implements Cloneable{ public int ID; public int age; public Address address; public int getID() { return ID; } public void setID(int iD) { ID = iD; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Customer(int iD, int age, Address address) { super(); ID = iD; this.age = age; this.address = address; } @Override public String toString() { return "Customer [ID=" + ID + ", age=" + age + ", address=" + address + "]"; } @Override public Customer clone() throws CloneNotSupportedException { return (Customer) super.clone(); } } class Address{ private String country; private String province; private String city; public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } @Override public String toString() { return "Address [country=" + country + ", province=" + province + ", city=" + city + "]"; } public Address(String country, String province, String city) { super(); this.country = country; this.province = province; this.city = city; } } //輸出的結果是: //customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=JN]] //customer2:Customer [ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
上面分析得到,clone後新舊物件互不影響,customer2修改了id後沒有影響到customer1,但是修改了customer2的address屬性的city值為JN後,發現customer1的address值也發生了改變。這樣就沒有達到完全複製、相互之間完全沒有影響的目的。這樣就需要進行深克隆。
2.深克隆
深克隆與淺克隆的區別就是,淺克隆不會克隆原物件中的引用型別,僅僅拷貝了引用型別的指向。深克隆則拷貝了所有。也就是說深克隆能夠做到原物件和新物件之間完全沒有影響。
而深克隆的實現就是在引用型別所在的類實現Cloneable介面,並使用public訪問修飾符重寫clone方法。
上面的程式碼做以下修改:
1.Address類實現Cloneable介面,重寫clone方法;
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
2.在Customer類的clone方法中呼叫Address類的clone方法。
@Override
public Customer clone() throws CloneNotSupportedException {
Customer customer = (Customer) super.clone();
customer.address = address.clone();
return customer;
}
修改後測試程式碼的輸出結果:
customer1:Customer[ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]
customer2:Customer[ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
發現customer2無論如何修改,customer1都沒有受到影響。
實現深克隆的另一種方法就是使用序列化,將物件寫入到流中,這樣物件的內容就變成了位元組流,也就不存在什麼引用了。然後讀取位元組流反序列化為物件就完成了完全的複製操作了。
Address address = new Address("CH" , "SD" , "QD");
Customer customer1 = new Customer(1 , 23 , address);
Customer customer2 = (Customer) cloneObject(customer1);
customer2.getAddress().setCity("JN");
customer2.setID(2);
System.out.println("customer1:"+customer1.toString());
System.out.println("customer2:"+customer2.toString());
//customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]
//customer2:Customer [ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
cloneObject方法的定義:
public static Object cloneObject(Object obj) throws IOException, ClassNotFoundException{
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(obj);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in =new ObjectInputStream(byteIn);
return in.readObject();
}
個人的理解是Java中定義的clone沒有深淺之分,都是統一的呼叫Object的clone方法。為什麼會有深克隆的概念?是由於我們在實現的過程中刻意的嵌套了clone方法的呼叫。也就是說深克隆就是在需要克隆的物件型別的類中全部實現克隆方法。就像是toString方法一樣,假如上面的Customer類中重寫了toString方法,而Address類沒有進行重寫,就會出現這樣的輸出語句:
customer1:Customer[ID=1, age=23, [email protected]]
只有在Address類也重寫了toString方法才會打印出完全的資訊:
customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]
只不過在列印的操作中就預設的呼叫了物件的toString方法,而clone方法需要在程式碼中顯式的呼叫。
總結:
1.淺克隆:只複製基本型別的資料,引用型別的資料只複製了引用的地址,引用的物件並沒有複製,在新的物件中修改引用型別的資料會影響原物件中的引用。 2.深克隆:是在引用型別的類中也實現了clone,是clone的巢狀,複製後的物件與原物件之間完全不會影響。 3.使用序列化也能完成深複製的功能:物件序列化後寫入流中,此時也就不存在引用什麼的概念了,再從流中讀取,生成新的物件,新物件和原物件之間也是完全互不影響的。 4.使用clone實現的深克隆其實是淺克隆中嵌套了淺克隆,與toString方法類似
--------------------- 本文來自 JeffCoding 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/jeffleo/article/details/76737560?utm_source=copy