1. 程式人生 > >Java深拷貝和淺拷貝(深克隆和淺克隆)

Java深拷貝和淺拷貝(深克隆和淺克隆)

Java中建立物件有兩種方式:

  1. 通過new操作符建立一個物件
  2. 通過clone方法來複制一個物件
  3. 使用反序列化來建立一個物件
  4. 通過使用Class類的newInstance方法來建立一個物件
  5. 使用Constructor類的newInstance方法來建立一個物件

第一種方法,通過new操作符來建立一個物件,分配記憶體,呼叫建構函式來填充各個域,這是我們最熟悉的;第二種clone也是分配記憶體,分配的記憶體和被clone物件相同,然後再使用原物件中對應的各個域,填充新物件的域, 填充完成之後,clone方法返回,一個新的相同的物件被建立,同樣可以把這個新物件的引用釋出到外部;第三種序列化和反序列化一個物件,jvm會給我們建立一個單獨的物件。在反序列化時,jvm建立物件並不會呼叫任何建構函式。第四種和第五種都是通過反射機制來建立一個物件,這裡就不細說了,接下來詳細說一下前三種情況。

在說之前先說一下引用拷貝

引用拷貝

public class Test {
	
	public static void main(String[] args) { Person person = new Person(); Person person1 = person; System.out.println(person.hashCode());//1426511082 System.out.println(person1.hashCode());//1426511082 } } class Person{ }

輸出結果:

1426511082
1426511082

 有上述程式碼列印雜湊值可知,person和person1指向一個記憶體地址,所以它們是一個物件,這裡只是拷貝了引用而已。

淺拷貝

要實現物件的拷貝,那麼要拷貝的這個物件的類必須實現Cloneable介面,並且重寫clone()方法,由於clone()方法在object類中被定義為protected,由於protected對本包和所有子類可以訪問,所以這裡無法呼叫到clone()方法,因而我們必須將覆寫的clone()方法宣告為public

public class Test {
	
	public static void main(String[] args) throws CloneNotSupportedException { Car car = new Car("red"); Person person = new Person("zhang",car); Person person1 = (Person)person.clone(); System.out.println(person.hashCode() == person1.hashCode());//false System.out.println(person.getCar().hashCode() == person1.getCar().hashCode());//true System.out.println(person.getName().hashCode() == person1.getName().hashCode());//true person1.getCar().setColor("blue"); person1.setName("liu"); System.out.println(person.getCar().getColor());//blue System.out.println(person1.getCar().getColor());//blue System.out.println(person.getName());//zhang System.out.println(person1.getName());//liu } } class Person implements Cloneable{ private String name; private Car car; public Person(String name,Car car) { this.car = car; this.name = name; } public Object clone() throws CloneNotSupportedException { // TODO Auto-generated method stub return super.clone(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } } class Car{ private String color; public Car(String color) { this.color = color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } } 

上述執行結果為:

false
true
true
blue
blue
zhang
liu
  • 第一個true表明person這個物件我們已經複製成功了,person和person1不是指向一個記憶體,perosn.name與person.name這是因為在Car類中我們並沒有覆寫clone()方法,這就是淺拷貝。
  • 第二個true說明了person.car與person1.car指向同一個地址,再將person1的car的屬性值為blue後,person的也隨之改變,這個也驗證了我們所說的person.car與person1.car指向同一個地址
  • 第三個true說明了perosn.name與person.name指向同一個地址,但是這時我們發現,person.name與person1.name不是一個值了,說明我們拷貝成功了,這是為什麼,這不和car屬性前後矛盾了?這是因為String類不是基本資料型別,而且直接被定義為final,而且沒有Cloneable介面,所以我們無法對它像其他pojo類一樣進行深拷貝,但這並不影響,因為String類是final,每次修改它都是重新建立新物件。
  • 假設person還有一個基本資料型別屬性,例如int型別的age屬性的話,覆寫了clone()方法後,直接拷貝是拷貝值,和String一樣,當值發生變化的時候會重新建立。

我們總結一下,淺拷貝又稱為淺複製,淺克隆,淺拷貝是指拷貝時只拷貝物件本身包括物件中的基本變數),而不拷貝物件包含的引用所指向的物件,拷貝出來的物件的所有變數的值都含有與原來物件相同的值,而所有對其他物件的引用都指向原來的物件,簡單地說,淺拷貝只是拷貝了物件本身,但是物件所引用的所有物件並沒有複製,只是複製了這個物件的引用,比如這兒的Car。

深拷貝

我們現在將Car類進行clone覆寫,並且將Person中的clone()進行改動,讓Person可以實現對Car屬性的深克隆

public class Test {
	
	public static void main(String[] args) throws CloneNotSupportedException { Car car = new Car("red"); Person person = new Person("zhang",car); Person person1 = (Person)person.clone(); System.out.println(person.hashCode() == person1.hashCode());//false System.out.println(person.getCar().hashCode() == person1.getCar().hashCode());//false System.out.println(person.getName().hashCode() == person1.getName().hashCode());//true person1.getCar().setColor("blue"); person1.setName("liu"); System.out.println(person.getCar().getColor());//red System.out.println(person1.getCar().getColor());//blue System.out.println(person.getName());//zhang System.out.println(person1.getName());//liu } } class Person implements Cloneable{ private String name; private Car car; public Person(String name,Car car) { this.car = car; this.name = name; } public Object clone() throws CloneNotSupportedException { // TODO Auto-generated method stub Person person = (Person)super.clone(); person.car = (Car)car.clone(); return person; } public void setName(String name) { this.name = name; } public void setCar(Car car) { this.car = car; } public String getName() { return name; } public Car getCar() { return car; } } class Car implements Cloneable{ private String color; public Car(String color) { this.color = color; } public String getColor() { return color; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } public void setColor(String color) { this.color = color; } } 

 輸出結果:

false
false
true
red
blue
zhang
liu

上述執行結果發生了改變,這次person和person.car的屬性按照我們的預期都深拷貝過來了,現在思考一下,如果是Car類中還有其他pojo類的時候怎麼辦?我們難道只能層層實現clone()方法嗎?那麼接下來我們可以使用反序列化的方法來clone()一個方法。

反序列化

序列化機制提供了一種克隆物件的簡便途徑,只要對應的類是可序列化的就可以,做法是直接將物件序列化到輸出流中,然後將其讀回,這樣產生的新的物件是對現有物件的一個深拷貝。在此過程中,我們不必將物件寫到檔案中,因為可以用ByteArrayOutputStream將資料儲存到位元組陣列中。經過測試,已經完成深拷貝

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class Test { public static void main(String[] args) throws Exception { Car car = new Car("red"); Person person = new Person("zhang",car, 0); Person person1 = (Person)person.deepClone(); System.out.println(person1); System.out.println(person.hashCode() == person1.hashCode());//false System.out.println(person.getCar().hashCode() == person1.getCar().hashCode());//false System.out.println(person.getName().hashCode() == person1.getName().hashCode());//true person1.getCar().setColor("blue"); person1.setName("liu"); person1.setAge(11); System.out.println(person.getCar().getColor());//red System.out.println(person1.getCar().getColor());//blue System.out.println(person.getName());//zhang System.out.println(person1.getName());//liu } } class Person implements Serializable{ private static final long serialVersionUID = 1L; private int age; private String name; private Car car; public Person(String name,Car car ,int age) { this.car = car; this.name = name; this.age = age; } public Object deepClone() throws Exception { //將物件寫到流裡 ByteArrayOutputStream bo=new ByteArrayOutputStream(); ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //從流裡讀出來 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=new ObjectInputStream(bi); return(oi.readObject()); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void setName(String name) { this.name = name; } public void setCar(Car car) { this.car = car; } public String getName() { return name; } public Car getCar() { return car; } } class Car implements Serializable{ private static final long serialVersionUID = 1L; private String color; public Car(String color) { this.color = color; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } } 

執行結果: 

[email protected]
false
false
true
red
blue
zhang
liu

如有錯誤歡迎您指出來,謝謝