克隆的目的:快速創建一個已有對象的副本。
克隆的步驟:
- 創建一個對象
- 將原有對象的數據導入到新創建的數據中
clone方法首先會判對象是否實現了Cloneable接口,若無則拋出CloneNotSupportedException, 最后會調用internalClone. intervalClone是一個native方法,一般來說native方法的執行效率高於非native方法。
源碼:
當某個類要復寫clone方法時,要繼承Cloneable接口。通常的克隆對象都是通過super.clone()方法來克隆對象。
2.淺克隆(shadow clone)
克隆就是復制一個對象的復本.若只需要復制對象的字段值(對於基本數據類型,如:int,long,float等,則復制值;對於復合數據類型僅復制該字段值,如數組變量則復制地址,對於對象變量則復制對象的reference。
- 實現Cloneable接口的類都具備被拷貝的能力,拷貝在內存中進行,比new生成對象性能明顯提升;
- clone方法是Object類的protected方法,也就是在用戶編寫的代碼中不能直接調用。為此必須重寫定義clone方法,並聲明為public,
- Cloneable接口的出現與接口正常使用沒有關系,是一個空接口,它只是一個標記表示一個對象需要克隆,如果一個對象需要克隆,而沒有實現Cloneable接口,就會產生一個CloneNotSupportedException異常
- Clone()方法存在與Object對象中,但是其不會將對象的全部屬性都拷貝,而是有選擇的拷貝
- 基本類型:
- 對象:如果變量是一個實例對象,只拷貝地址引用;
- String字符串:拷貝地址引用,但是修改時,會從字符串池中重新生成新的字符串,原來的保持不變;
- 對於Clone實現對象的拷貝:需要新建大量的對象,工程量大可以使用序列化來實現對象的拷貝
需要clone的對象:
package com.gzu.pyu.thinking.in.java.practice0211; import java.io.Serializable; public class Student implements Cloneable ,Serializable { public String name; public int age; public Address address; public Student() { } public Student(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } public Address getAddress() { return address; } public void setAddress(Address address) { this.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; } @Override public Object clone() { Student student=null; try { //淺拷貝 student= (Student)super.clone(); } catch (CloneNotSupportedException e) { return new Student(); } //支持深度克隆 student.address=address.clone(); return student; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Student{"); sb.append("name='").append(name).append('\''); sb.append(", age=").append(age); sb.append(", address=").append(address); sb.append('}'); return sb.toString(); } } class Address implements Cloneable,Serializable{ String city; String town; public Address() { } public Address(String city, String town) { this.city = city; this.town = town; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getTown() { return town; } public void setTown(String town) { this.town = town; } @Override public Address clone() { Address address=null; try { address= (Address) super.clone(); } catch (CloneNotSupportedException e) { return new Address(); } return address; } @Override public String toString() { final StringBuffer sb = new StringBuffer("Address{"); sb.append("city='").append(city).append('\''); sb.append(", town='").append(town).append('\''); sb.append('}'); return sb.toString(); } }
測試方法如下:
package com.gzu.pyu.thinking.in.java.practice0211; import com.gzu.pyu.thinking.in.java.utils.CloneUtils; public class TestMain { public static void main(String ... args){ Address address=new Address("ShengZheng","LongGang"); //將address指向的區域賦值給addressNew,使得addressNew和address同時指向了同一個區域 Address addressNew=address; //address指向的內容改變了,addressNew指向的內容也跟着改變 System.out.println(address==addressNew);// true address.setCity("GuiYang"); System.out.println(address); // Address{city='GuiYang', town='LongGang'} System.out.println(addressNew); // Address{city='GuiYang', town='LongGang'} //address進行克隆,將address進行了復制了,產生了一個新的對象 Address addressClone = address.clone(); System.out.println(address==addressClone);// false address.setTown("HuaXi"); System.out.println(address); // Address{city='GuiYang', town='HuaXi'} System.out.println(addressClone); // Address{city='GuiYang', town='LongGang'} //在內存中通過字節流的拷貝是比較容易的,把木對象寫入到一個字節流中,再從字節流中讀出來 Address addr = CloneUtils.clone(address); System.out.println(address==addr);// false addr.setCity("PanZhou"); System.out.println(address); // Address{city='GuiYang', town='HuaXi'} System.out.println(addr); // Address{city='PanZhou', town='HuaXi'} } }
二、使用序列化來實現對象的拷貝
- 在內存中通過字節流的拷貝是比較容易的,把木對象寫入到一個字節流中,再從字節流中毒出來,這樣可以創建一個新的對象,且新對象與木對象不存在引用共享的問題,實現的對象的深拷貝
package com.gzu.pyu.thinking.in.java.utils; import java.io.*; /** * 在內存中通過字節流的拷貝是比較容易的,把木對象寫入到一個字節流中,再從字節流中讀出來, * 這樣可以創建一個新的對象,且新對象與木對象不存在引用共享的問題,實現的對象的深拷貝 */ public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj){ T cloneObj = null; //寫入字節流 try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new ObjectOutputStream(out); obs.writeObject(obj); obs.close(); //分配內存,寫入原始對象,生成新對象 ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(ios); //返回生成的新對象 cloneObj = (T) ois.readObject(); ois.close(); }catch(IOException e){ e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return cloneObj; } }