天天看點

JAVA深克隆和淺克隆

一、基礎概念

1. 淺克隆(淺複制)

建立一個新對象,新對象的屬性和原來的對象完全相同,對于非基本資料類型屬性(即引用類型),扔指向原有屬性所指向的對象的記憶體位址

2. 深克隆(深複制)

建立一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象位址。換言之,深複制把要複制的對象所引用的對象都複制了一遍。

二、JAVA的clone()方法

clone方法将對象複制了一份并傳回給調用者。一般而言,clone()方法滿足

  1. 對任何的對象x,都有x.clone() !=x  因為克隆對象與原對象不是同一個對象
  2. 對任何的對象x,都有x.clone().getClass()= =x.getClass()//克隆對象與原對象的類型一樣
  3. 如果對象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