一、基礎概念
1. 淺克隆(淺複制)
建立一個新對象,新對象的屬性和原來的對象完全相同,對于非基本資料類型屬性(即引用類型),扔指向原有屬性所指向的對象的記憶體位址
2. 深克隆(深複制)
建立一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象位址。換言之,深複制把要複制的對象所引用的對象都複制了一遍。
二、JAVA的clone()方法
clone方法将對象複制了一份并傳回給調用者。一般而言,clone()方法滿足
- 對任何的對象x,都有x.clone() !=x 因為克隆對象與原對象不是同一個對象
- 對任何的對象x,都有x.clone().getClass()= =x.getClass()//克隆對象與原對象的類型一樣
- 如果對象x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立
三、實作方法
淺克隆:
一、通過拷貝構造方法實作淺拷貝:
拷貝構造方法指的是該類的構造方法參數為該類的對象。使用拷貝構造方法可以很好地完成淺拷貝,直接通過一個現有的對象建立出與該對象屬性相同的新的對象。
package com.cn.prototype.copy.qian;
public class Age {
private int age;
public Age(int age) {
this.setAge(age);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return getAge()+"";
}
}
package com.cn.prototype.copy.qian;
public class Person {
private Age age; //引用傳遞
private String name; //值傳遞
public Person(Age age, String name) {
this.age = age;
this.name = name;
}
//構造方法實作淺克隆
public Person(Person p) {
this.name = p.name;
this.age = p.age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name+" "+this.age;
}
}
package com.cn.prototype.copy.qian;
public class Test {
public static void main(String[] args) {
Age age = new Age(19);
Person p1 = new Person(age, "小明");
Person p2 = new Person(p1);
System.out.println("p1是"+p1);
System.out.println("p2是"+p2);
//修改p1的各屬性值,觀察p2的各屬性值是否跟随變化
p1.setName("小紅");
age.setAge(99);
System.out.println("修改後的p1是"+p1);
System.out.println("修改後的p2是"+p2);
}
}
運作結果:
p1是小明 19
p2是小明 19
修改後的p1是小紅 99
修改後的p2是小明 99
結果分析:
通過拷貝構造方法進行了淺拷貝,各屬性值成功複制。其中,p1值傳遞部分的屬性值發生變化時,p2不會随之改變;而引用傳遞部分屬性值發生變化時,p2也随之改變。
要注意:如果在拷貝構造方法中,對引用資料類型變量逐一開辟新的記憶體空間,建立新的對象,也可以實作深拷貝。而對于一般的拷貝構造,則一定是淺拷貝。
二、通過重寫clone() 方法進行淺拷貝
Object類是類結構的根類,其中有一個方法為protected Object clone() throws CloneNotSupportedException,這個方法就是進行的淺拷貝。有了這個淺拷貝模闆,我們可以通過調用clone()方法來實作對象的淺拷貝。但是需要注意:
1、Object類雖然有這個方法,但是這個方法是受保護的(被protected修飾),是以我們無法直接使用。
2、使用clone方法的類必須實作Cloneable接口,否則會抛出異常CloneNotSupportedException。對于這兩點,我們的解決方法是,在要使用clone方法的類中重寫clone()方法,通過super.clone()調用Object類中的原clone方法。
package com.cn.prototype.copy.deep;
public class Age {
private int age;
public Age(int age) {
this.setAge(age);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return getAge()+"";
}
}
package com.cn.prototype.copy.deep;
public class Person implements Cloneable {
private Age age; //引用傳遞
private String name; //值傳遞
public Person(Age age, String name) {
this.age = age;
this.name = name;
}
@Override
protected Object clone() {
Object o = null;
try {
o = (Person)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name+" "+this.age;
}
}
package com.cn.prototype.copy.deep;
public class Test {
public static void main(String[] args) {
Age age = new Age(19);
Person p1 = new Person(age, "小明");
Person p2 = (Person) p1.clone();
System.out.println("p1是"+p1);
System.out.println("p2是"+p2);
//修改p1的各屬性值,觀察p2的各屬性值是否跟随變化
p1.setName("小紅");
age.setAge(99);
System.out.println("修改後的p1是"+p1);
System.out.println("修改後的p2是"+p2);
}
}
運作結果:
p1是小明 19
p2是小明 19
修改後的p1是小紅 99
修改後的p2是小明 99
分析結果可以驗證:
基本資料類型是值傳遞,是以修改值後不會影響另一個對象的該屬性值;
引用資料類型是位址傳遞(引用傳遞),是以修改值後另一個對象的該屬性值會同步被修改。
String類型非常特殊,首先,String類型屬于引用資料類型,不屬于基本資料類型,但是String類型的資料是存放在常量池中的,也就是無法修改的!也就是說,當我将String類型屬性從“a”改為“ab"後,并不是修改了這個資料的值,而是把這個資料的引用從指向”a“這個常量改為了指向”ab“這個常量。在這種情況下,另一個對象的name屬性值仍然指向”a“不會受到影響。
深拷貝:
首先介紹對象圖的概念。設想一下,一個類有一個對象,其成員變量中又有一個對象,該對象指向另一個對象,另一個對象又指向另一個對象,直到一個确定的執行個體。這就形成了對象圖。那麼,對于深拷貝來說,不僅要複制對象的所有基本資料類型的成員變量值,還要為所有引用資料類型的成員變量申請存儲空間,并複制每個引用資料類型成員變量所引用的對象,直到該對象可達的所有對象。也就是說,對象進行深拷貝要對整個對象圖進行拷貝!
一、通過重寫clone方法來實作深拷貝
package com.cn.prototype.copy.deep;
public class Age implements Cloneable{
private int age;
public Age(int age) {
this.setAge(age);
}
@Override
protected Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return getAge()+"";
}
}
package com.cn.prototype.copy.deep;
public class Person implements Cloneable {
private Age age; //引用傳遞
private String name; //值傳遞
public Person(Age age, String name) {
this.age = age;
this.name = name;
}
@Override
protected Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//把它裡面的age對象也進行clone
Person person = (Person) o;
person.age = (Age) person.age.clone();
return o;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name+" "+this.age;
}
}
package com.cn.prototype.copy.deep;
public class Test {
public static void main(String[] args) {
Age age = new Age(19);
Person p1 = new Person(age, "小明");
Person p2 = (Person) p1.clone();
System.out.println("p1是"+p1);
System.out.println("p2是"+p2);
//修改p1的各屬性值,觀察p2的各屬性值是否跟随變化
p1.setName("小紅");
age.setAge(99);
System.out.println("修改後的p1是"+p1);
System.out.println("修改後的p2是"+p2);
}
}
結果:
p1是小明 19
p2是小明 19
修改後的p1是小紅 99
修改後的p2是小明 19
二、通過對象序列化進行深克隆
雖然層次調用clone方法可以實作深拷貝,但是顯然代碼量實在太大。特别對于屬性數量比較多、層次比較深的類而言,每個類都要重寫clone方法太過繁瑣。
将對象序列化為位元組序列後,預設會将該對象的整個對象圖進行序列化,再通過反序列即可完美地實作深拷貝。
package com.cn.prototype.copy.deep;
import java.io.Serializable;
public class Age implements Serializable{
private static final long serialVersionUID = -7045379962789920819L;
private int age;
public Age(int age) {
this.setAge(age);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return getAge()+"";
}
}
package com.cn.prototype.copy.deep;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = 2154564168527528525L;
private Age age; //引用傳遞
private String name; //值傳遞
public Person(Age age, String name) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name+" "+this.age;
}
}
package com.cn.prototype.copy.deep;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws Exception {
Age age = new Age(19);
Person p1 = new Person(age, "小明");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(p1);
oos.flush();
ObjectInputStream ois=new ObjectInputStream(new
ByteArrayInputStream(bos.toByteArray()));
Person p2 = (Person) ois.readObject();
System.out.println("p1是"+p1);
System.out.println("p2是"+p2);
//修改p1的各屬性值,觀察p2的各屬性值是否跟随變化
p1.setName("小紅");
age.setAge(99);
System.out.println("修改後的p1是"+p1);
System.out.println("修改後的p2是"+p2);
}
}
結果:
p1是小明 19
p2是小明 19
修改後的p1是小紅 99
修改後的p2是小明 19