談談java中對象的深拷貝與淺拷貝


知識點:java中關於Object.clone方法,對象的深拷貝與淺拷貝

引言

       在一些場景中,我們需要獲取到一個對象的拷貝,這時候就可以用java中的Object.clone方法進行對象的復制,得到一個一模一樣的新對象。(使用new再次創建一個相同的對象也是可以的,有些區別),當對象中含有可變的引用類型屬性時,在復制得到的新對象對該引用類型屬性內容進行修改,原始對象相應的屬性內容也會發生變化,這就是"淺拷貝"的現象。反之原始對象相應的引用類型屬性不發生變化,是 "深拷貝"。

 

淺拷貝

先看一個淺拷貝的demo實例

1.創建一個Person類

public class Person implements Cloneable{
private String name;
private int age;
private Address address;

//構造方法
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

public String display(){
return "Person[name="+name+",age="+age+",address"+address+"]";
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

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;
}
}

由於clone()方法是protected修飾的,因此需要實現Cloneable接口才能調用,同時需要覆寫clone()方法才能調用。

2.創建一個Address類
public class Address {
private String province;//省份
private String city;//所在城市

//構造方法
public Address(String province, String city) {
this.province = province;
this.city = city;
}

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[province="+province+",city="+city+"]";
}
}


3.淺拷貝測試
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person=new Person("張三",20,new Address("安徽","合肥"));

Person clonePerson=(Person) person.clone();

System.out.println(person);
System.out.println(clonePerson);

System.out.println(person.display());
System.out.println(clonePerson.display());

clonePerson.setName("李四");
clonePerson.setAge(22);
Address address=clonePerson.getAddress();
address.setProvince("江蘇");

System.out.println(person.display());
System.out.println(clonePerson.display());
}
}



4.代碼運行結果:

   第1、2句輸出說明了原對象與新對象是兩個不同的對象。

   第3、4句可以看到拷貝出來的新對象與原對象內容一致

  但是,接着將新對象里面的信息進行了修改,然后輸出發現原對象里面的部分信息也跟着變了。仔細觀察發現原對象跟着變化的只是Address部分,這就跟clone本身的淺拷貝有關系了。

   淺拷貝  :創建一個新對象,然后將當前對象的非靜態字段復制到該對象,如果字段類型是值類型(基本類型)的,那么對該字段進行復制;如果字段是引用類型的,則只復制該字段的引用而不復制引用指向的對象。此時新對象里面的引用類型字段相當於是原始對象里面引用類型字段的一個副本,原始對象與新對象里面的引用字段指向的是同一個對象。

簡而言之:類實現默認的Object.clone()方法,拷貝對象時,對於引用類型的成員變量(屬性)拷貝只是拷貝“值”即地址(引用),沒有在堆中開辟新的內存空間。

因此,修改clonePerson里面的address內容時,原person里面的address內容會跟着改變。

 

  “深拷貝” :類重寫clone()方法,對於引用類型成員變量,重新在堆中開辟新的內存空間,簡單地說,將引用類型的屬性內容也拷貝一份新的。

如果我們想實現深拷貝,有兩種方法,第一種是給需要拷貝的引用類型也實現Cloneable接口並覆寫clone方法;第二種則是利用序列化。

具體代碼演示可參考:https://www.cnblogs.com/nickhan/p/8569329.html

 

接下來補充一下,涉及到的知識點

復制對象和復制引用的區別

 

 

String的特殊性性

其實string類型也是引用類型,不是基本類型,那上面的例子中,修改拷貝對象person的name后,原對象的name值並沒有改變,這是什么原因呢?

答:其實string也是引用類型,在深復制時並沒有進行單獨復制,僅僅復制了引用,String並沒有實現cloneable接口,之所以修改新克隆對象的name值后,

原對象的name對應的值沒有改變,是因為String在內存中是不可以改變的對象,我們在修改新對象(或者原對象)的name值時,會新分配一塊內存保存新修改的name值,新對象的引用(或者原對象的引用)會指向新的內存空間,原來的String 還會存在它的引用name,如果String的值不存在引用,會被回收,也因為原來的String值存在引用name,所以不會被回收,這樣,可以解釋上面的demo中,name雖然是String引用類型,但是修改name的值,並沒有改變原對象的name值。

 我們也可以在idea中,用debugger調試驗證一下

 執行上面代碼前三行代碼,debugger

上面的箭頭可以看出person和clonePerson的name是指向同一對象的引用(@468)

修改其中的對象的name值debugger看看

當我們執行clonePerson.setName("李四");修改新克隆對象的name值以后,則:

 

新克隆對象的name引用對應的值,發生了改變,原對象的引用對應的內存值沒有發生變化

克隆一個對象與new一個對象的區別

(1)java中clone和new都可以創建一個新的對象

(2)clone()不會調用構造方法,而new一個對象會調用類的構造方法

(3)clone()能快速創建一個已有對象的副本,即創建對象並且將已有對象中所有屬性值克隆;new只能在JVM中申請一個空的內存區域,對象的屬性值要通過構造方法賦值。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM