java對象clone

  • 2020 年 4 月 10 日
  • 筆記

java克隆

為什麼需要克隆

我們在很多時候需要使用一個對象去記錄另外一個對象的當前狀態,對象中可能會有很多屬性,如果我們一個一個去設置,不僅不方便,而且效率很低,我們看一個初學者可能遇到的問題

class Person{  	String name;      int age;    	public Person() {}  	public Person(String name, int age) {  		super();  		this.name = name;  		this.age = age;  	}  	@Override  	public String toString() {  		return "Person [name=" + name + ", age=" + age + "]";  	}    }    @Test  	public void test2() {  		Person p1=new Person("tom",20);  		Person p2=p1;  		System.out.println(p1);  		System.out.println(p2);  		System.out.println("---------");  		p1.age=30;  		System.out.println(p1);  		System.out.println(p2);  	}  輸出:  Person [name=tom, age=20]  Person [name=tom, age=20]  ---------  Person [name=tom, age=30]  Person [name=tom, age=30]    

也許有的人認為Person p2=p1這樣的方式就可以克隆一個對象,這種想法是錯誤的,這種使用等號賦值的方式只是將p1的地址賦值給了p2對象,那麼p1和p2都指向了堆中的同一個對象,所以,修改p1那麼p2也就變了

如果一個一個屬性的去手動設置不僅麻煩,而且屬性可能也有屬性,修改起來不容易

public void test2() {  		Person p1=new Person("tom",20);  		Person p2=new Person();  		p2.age=p1.age;  		p2.name=p1.name;  	}  

這裡Person的屬性層級很簡單,修改起來看起來很簡單,但是如果Person多了一個Address類型的屬性,那麼手動修改就必須要去new一個Address並賦值屬性,假如屬性的嵌套層級深了,就很難了

所以我們可以使用Object提供的clone方法來克隆對象,由於clone方法是用protected關鍵字修飾的,我們如果想在類外使用就需要重寫父類的方法,Object的clone方法是一個native關鍵字修飾的方法,即調用其他語言的方法,效率很高,值得注意的一點是要克隆對象的類必須實現Cloneable介面,這個介面是一個標記介面,裡面並沒有定義任何方法,如果沒事實現Cloneable介面使用clone方法,會拋出CloneNotSupportedException異常

淺克隆

如果原型對象的屬性是值類型,那麼複製一份給克隆對象,如果屬性是引用類型,那麼把屬性的引用賦值給克隆對象

class Person implements Cloneable{//必須實現Cloneable介面  	String name;      int age;    	public Person() {}  	public Person(String name, int age) {  		super();  		this.name = name;  		this.age = age;  	}  	@Override  	public String toString() {  		return "Person [name=" + name + ", age=" + age + "]";  	}  	@Override  	protected Object clone(){  		Person p=null;  		try {  			p=(Person)super.clone();  		}catch(CloneNotSupportedException e) {//實現了Cloneable介面這個異常就不可能發生  			e.printStackTrace();  		}  		return p;  	}    }    @Test  	public void test2() {  		Person p1=new Person("tom",20);  		Person p2=(Person)p1.clone();  		System.out.println(p1);  		System.out.println(p2);  		System.out.println("---------");  		p1.age=30;  		System.out.println(p1);  		System.out.println(p2);  	}  輸出:  Person [name=tom, age=20]  Person [name=tom, age=20]  ---------  Person [name=tom, age=30]  Person [name=tom, age=20]    

這種Object提供的clone在類中的屬性全是值類型的時候不會出現問題,但是如果類屬性有引用類型就會出現問題

class Person implements Cloneable{  	String name;      int age;      Address address;    	public Person() {}  	public Person(String name, int age,Address addr) {  		super();  		this.name = name;  		this.age = age;  		this.address=addr;  	}    	@Override  	public String toString() {  		return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";  	}  	@Override  	protected Object clone(){  		Person p=null;  		try {  			p=(Person)super.clone();  		}catch(CloneNotSupportedException e) {  			e.printStackTrace();  		}  		return p;  	}    }  class Address{  	String addr;    	public Address(String addr) {  		super();  		this.addr = addr;  	}    	@Override  	public String toString() {  		return "Address [addr=" + addr + "]";  	}    }    @Test  	public void test2() {  		Address addr=new Address("成都市郫都區");  		Person p1=new Person("tom",20,addr);  		Person p2=(Person)p1.clone();  		System.out.println(p1);  		System.out.println(p2);  		System.out.println("---------");  		p1.address.addr="成都市金牛區";  		System.out.println(p1);  		System.out.println(p2);  	}  輸出:  Person [name=tom, age=20, address=Address [addr=成都市郫都區]]  Person [name=tom, age=20, address=Address [addr=成都市郫都區]]  ---------  Person [name=tom, age=20, address=Address [addr=成都市金牛區]]  Person [name=tom, age=20, address=Address [addr=成都市金牛區]]    

修改p1的address根據p1克隆出來的p2的address也改變了,這就是所說的當克隆對象的屬性是引用類型時,只會複製它的引用,而這種情況一般是我們不希望的,所以需要使用深克隆

深克隆

1.克隆對象所有的引用類型都重寫clone方法

這裡所說的引用類型重寫clone方法,是指克隆對象本身屬性是引用類型的必須重寫clone方法且引用類型中的引用類型也必要要重寫,且必須在clone方法中顯式條用

class Person implements Cloneable{  	String name;      int age;      Address address;//這個屬性是引用類型,必須實現Cloneable介面重寫clone方法    	public Person() {}  	public Person(String name, int age,Address addr) {  		super();  		this.name = name;  		this.age = age;  		this.address=addr;  	}    	@Override  	public String toString() {  		return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";  	}  	@Override  	protected Object clone(){  		Person p=null;  		try {  			p=(Person)super.clone();  		}catch(CloneNotSupportedException e) {  			e.printStackTrace();  		}        //顯式調用克隆引用數據類型  		p.address=(Address)address.clone();  		return p;  	}    }  class Address implements Cloneable{  	String addr;    	public Address(String addr) {  		super();  		this.addr = addr;  	}    	@Override  	public String toString() {  		return "Address [addr=" + addr + "]";  	}    	@Override  	protected Object clone() {  		Address addr=null;  		try {  			addr=(Address)super.clone();  		}catch(CloneNotSupportedException e) {  			e.printStackTrace();  		}  		return addr;  	}    }    @Test  	public void test2() {  		Address addr=new Address("成都市郫都區");  		Person p1=new Person("tom",20,addr);  		Person p2=(Person)p1.clone();  		System.out.println(p1);  		System.out.println(p2);  		System.out.println("---------");  		p1.address.addr="成都市金牛區";  		System.out.println(p1);  		System.out.println(p2);  	}  輸出:  Person [name=tom, age=20, address=Address [addr=成都市郫都區]]  Person [name=tom, age=20, address=Address [addr=成都市郫都區]]  ---------  Person [name=tom, age=20, address=Address [addr=成都市金牛區]]  Person [name=tom, age=20, address=Address [addr=成都市金牛區]]    

使用序列化方法

需要克隆的對象類必須實現Serializable介面,這個介面也是一個標記介面,介面中沒有任何方法,這個方法的實現類表示可以將這個類的對象寫入到IO流中,那麼久可以把對象在網路中發送或保存到本地磁碟

class Person implements Cloneable,Serializable{  	/**  	 *  	 */  	private static final long serialVersionUID = 8990580911834489134L;  	String name;      int age;      Address address;    	public Person() {}  	public Person(String name, int age,Address addr) {  		super();  		this.name = name;  		this.age = age;  		this.address=addr;  	}    	@Override  	public String toString() {  		return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";  	}  	@Override  	protected Object clone(){  		Person p=null;  		try {  			//將對象寫入流中  			ByteArrayOutputStream baos=new ByteArrayOutputStream();  			ObjectOutputStream oos=new ObjectOutputStream(baos);  			oos.writeObject(this);  			//將對象從流中讀取出來  			ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());  			ObjectInputStream ois=new ObjectInputStream(bais);  			p=(Person)ois.readObject();  		} catch (ClassNotFoundException e) {  			e.printStackTrace();  		} catch (IOException e) {  			e.printStackTrace();  		}  		return p;  	}    }  class Address implements Serializable{  	/**  	 *  	 */  	private static final long serialVersionUID = 328854588872604721L;  	String addr;    	public Address(String addr) {  		super();  		this.addr = addr;  	}    	@Override  	public String toString() {  		return "Address [addr=" + addr + "]";  	}  }    @Test  	public void test2() {  		Address addr=new Address("成都市郫都區");  		Person p1=new Person("tom",20,addr);  		Person p2=(Person)p1.clone();  		System.out.println(p1);  		System.out.println(p2);  		System.out.println("---------");  		p1.address.addr="成都市金牛區";      		System.out.println(p1);  		System.out.println(p2);  	}  輸出:  Person [name=tom, age=20, address=Address [addr=成都市郫都區]]  Person [name=tom, age=20, address=Address [addr=成都市郫都區]]  ---------  Person [name=tom, age=20, address=Address [addr=成都市金牛區]]  Person [name=tom, age=20, address=Address [addr=成都市郫都區]]    

Tags: