1.為什么需要克隆?
在實際編程過程中,我們常常要遇到這種情況:有一個對象A,在某一時刻A中已經包含了一些有效值,此時可能會需要一個和A完全相同新對象B,並且此后對B任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。在Java語言中,用簡單的賦值語句是不能滿足這種需求的,要滿足這種需求有很多途徑。看例子:
public class Test implements Cloneable { public String str; public Test(){
System.out.println("Test的構造方法");
}
public String getStr(){ return str; } public void setStr(String str){ this.str = str; } public Object clone() throws CloneNotSupportedException{ return super.clone(); } public static void main(String[] args) throws CloneNotSupportedException{ Test t1 = new Test(); t1.setStr("1"); System.out.println(t1.getStr()); Test t2 = new Test(); t2=t1; t1.setStr("222"); System.out.println(t1.getStr()); System.out.println(t2.getStr()); } }
運行結果:
1 222 222
Java底層使用C/C++實現的,"="這個運算符,如果左右兩邊都是對象引用的話,在Java中表示的將等號右邊的引用賦值給等號左邊的引用,二者指向的還是同一塊內存,所以任何一個引用對內存的操作都直接反映到另一個引用上。
但是,現在我想拿這個so0的數據進行一些操作,不想改變原來so0中的內容,這時候就可以使用克隆了,它允許在堆中克隆出一塊和原對象一樣的對象,並將這個對象的地址賦予新的引用,這樣,顯然我對新引用的操作,不會影響到原對象。
2.淺克隆
淺度克隆對於要克隆的對象,對於其基本數據類型的屬性,復制一份給新產生的對象,對於非基本數據類型的屬性,僅僅復制一份引用給新產生的對象,即新產生的對象和原始對象中的非基本數據類型的屬性都指向的是同一個對象。
淺克隆的步驟:
1.實現java.lang.Cloneable接口
(1):實現Cloneable接口,不包含任何方法!僅僅是用來指示Object類中clone()方法可以用來合法的進行克隆。
(2):如果在沒有實現Cloneable接口的實例上調用Object的clone()方法,則會導致拋出CloneNotSupporteddException
(3): 實現此接口的類,應該使用public,重寫Object的clone方法。Object類中的clone()是一個protected屬性的方法,重寫之后要把clone()方法的屬性設置為public。因為在Object類中的clone()方法是一個native方法,native方法的效率一般來說都是遠高於java中的非native方法。這也解釋了為什么要用Object中clone()方法而不是先new一個類,然后把原始對象中的信息賦到新對象中,雖然這也實現了clone功能。
2.重寫java.lang.Object.clone()方法
創建並返回此對象的一個副本。對於任何對象x,表達式:
(1)x.clone() != x為true
(2)x.clone().getClass() == x.getClass()為true
(3)x.clone().equals(x)一般情況下為true,但這並不是必須要滿足的要求
Object類中clone()方法產生的效果是:先在內存中開辟一塊和原始對象一樣的空間,然后原樣拷貝原始對象中的內容。對基本數據類型,這樣的操作是沒有問題的,但對非基本類型變量,我們知道它們保存的僅僅是對象的引用,這也導致clone后的非基本類型變量和原始對象中相應的變量指向的是同一個對象。
實例:將上面例子改寫一下:
public static void main(String[] args) throws CloneNotSupportedException{ Test t1 = new Test(); t1.setStr("1"); Test t2 = (Test)t1.clone(); System.out.println("so0 == so1?" + (t1 == t2)); System.out.println("so0.getClass() == so1.getClass()?" + (t1.getClass() == t2.getClass())); System.out.println("so0.equals(so1)?" + (t1.equals(t2))); t1.setStr("222"); System.out.println("so0.getStr():" + t1.getStr()); System.out.println("so1.getStr():" + t2.getStr()); }
輸出結果:
TEST的構造方法 so0 == so1?false so0.getClass() == so1.getClass()?true so0.equals(so1)?false so0.getStr():222 so1.getStr():1
得到三個結論:
1、克隆一個對象並不會調用對象的構造方法,因為"Test的構造方法"語句只出現了一次
2、符合JDK API的clone()方法三條規則
3、t2對Test對象str字段的修改再也不會影響到t1了
3.深克隆
深克隆簡單的說就是:除了克隆自身對象,還對其他非基本數據類型的引用的其他以外的所有對象,都克隆了一遍。
深克隆的步驟:
1.首先克隆的類,也必須實現Cloneable接口和重寫Object的clone()的方法。
2.在不引入第三方jar包的情況下,可以使用兩種辦法:
(1)、先對對象進行序列化,緊接着馬上反序列化出。
(2)、先調用super.clone()方法克隆出一個新對象來,然后在子類的clone()方法中手動給克隆出來的非基本數據類型(引用類型)賦值,比如ArrayList的clone()方法:
public Object clone() { try { ArrayList<E> v = (ArrayList<E>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } }
4.第三方jar包
對於克隆,java還提供了一些好用的第三方jar包,比如:
(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)都是相當於克隆中的淺克隆。