java基礎學習總結(七):Cloneable介面和Object的clone()方法
為什麼要克隆
為什麼要使用克隆,這其實反映的是一個很現實的問題,假如我們有一個物件:
public class SimpleObject implements Cloneable { private String str; public SimpleObject() { System.out.println("Enter SimpleObject.constructor()"); } 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) { SimpleObject so0 = new SimpleObject(); so0.setStr("111"); System.out.println("so0.getStr():" + so0.getStr()); SimpleObject so1 = so0; so1.setStr("222"); System.out.println("so0.getStr():" + so0.getStr()); System.out.println("so1.getStr():" + so1.getStr()); }
執行結果其實很明顯:
so0.getStr():111
so0.getStr():222
so1.getStr():222
Java底層使用C/C++實現的,"="這個運算子,如果左右兩邊都是物件引用的話,在Java中表示的將等號右邊的引用賦值給等號左邊的引用,二者指向的還是同一塊記憶體,所以任何一個引用對記憶體的操作都直接反映到另一個引用上。
但是,現在我想拿這個so0的資料進行一些操作,不想改變原來so0中的內容,這時候就可以使用克隆了,它允許在堆中克隆出一塊和原物件一樣的物件,並將這個物件的地址賦予新的引用,這樣,顯然我對新引用的操作,不會影響到原物件。
Cloneable介面和Object的clone()方法
Java中實現了Cloneable介面的類有很多,像我們熟悉的ArrayList、Calendar、Date、ashMap、Hashtable、HashSet、LinkedList等等。
還是那句話,對於不熟悉的介面、方法,第一反應一定是查詢JDK API。
1、Cloneable介面
- 實現了Cloneable介面,就是在執行時期通知Java虛擬機器可以安全地在這個類上使用clone()方法
- 如果在沒有實現Cloneable介面的例項上呼叫Object的clone()方法,則會導致丟擲CloneNotSupporteddException
- 實現此介面的類應該使用公共方法重寫Object的clone()方法,Object的clone()方法是一個受保護的方法
2、Object的clone()方法
建立並返回此物件的一個副本。對於任何物件x,表示式:
(1)x.clone() != x為true
(2)x.clone().getClass() == x.getClass()為true
(3)x.clone().equals(x)一般情況下為true,但這並不是必須要滿足的要求
克隆例項
把上面例子的main函式修改一下:
public static void main(String[] args) throws Exception
{
SimpleObject so0 = new SimpleObject();
so0.setStr("111");
SimpleObject so1 = (SimpleObject)so0.clone();
System.out.println("so0 == so1?" + (so0 == so1));
System.out.println("so0.getClass() == so1.getClass()?" + (so0.getClass() == so1.getClass()));
System.out.println("so0.equals(so1)?" + (so0.equals(so1)));
so1.setStr("222");
System.out.println("so0.getStr():" + so0.getStr());
System.out.println("so1.getStr():" + so1.getStr());
}
看一下執行結果:
Enter SimpleObject.constructor()
so0 == so1?false
so0.getClass() == so1.getClass()?true
so0.equals(so1)?false
so0.getStr():111
so1.getStr():222
得到三個結論:
1、克隆一個物件並不會呼叫物件的構造方法,因為"Enter SimpleObject.constructor()"語句只出現了一次
2、符合JDK API的clone()方法三條規則
3、so1對於SimpleObject物件str欄位的修改再也不會影響到so0
淺克隆和深克隆
淺克隆(shallow clone)和深克隆(deep clone)反映的是,當物件中還有物件的時候,那麼:
1、淺克隆,即很表層的克隆,如果我們要克隆物件,只克隆它自身以及它所包含的所有物件的引用地址
2、深克隆,克隆除自身物件以外的所有物件,包括自身所包含的所有物件例項
這兩個概念應該很好理解,就不寫程式碼了。多提一句,所有的基本資料型別,無論是淺克隆還是深克隆,都會進行原值克隆,畢竟它們都不是物件,不是儲存在堆中的。
那其實Object的clone()方法,提供的是一種淺克隆的機制,如果想要實現對物件的深克隆,在不引入第三方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();
}
}