深克隆和淺克隆有什么區(qū)別?它的實(shí)現(xiàn)方式有哪些?
什么是淺克隆和深克隆
淺克隆(Shadow Clone)是把原型對象中成員變量為值類型的屬性都復(fù)制給克隆對象,把原型對象中成員變量為引用類型的引用地址也復(fù)制給克隆對象,也就是原型對象中如果有成員變量為引用對象,則此引用對象的地址是共享給原型對象和克隆對象的。
簡單來說就是淺克隆只會復(fù)制原型對象,但不會復(fù)制它所引用的對象

深克隆(Deep Clone)是將原型對象中的所有類型,無論是值類型還是引用類型,都復(fù)制一份給克隆對象,也就是說深克隆會把原型對象和原型對象所引用的對象,都復(fù)制一份給克隆對象,如下圖所示:

如何實(shí)現(xiàn)克隆
在 Java 語言中要實(shí)現(xiàn)克隆則需要實(shí)現(xiàn) Cloneable 接口,并重寫 Object 類中的 clone() 方法,實(shí)現(xiàn)代碼如下:
public class CloneExample {
public static void main(String[] args) throws CloneNotSupportedException {
// 創(chuàng)建被賦值對象
People p1 = new People();
p1.setId(1);
p1.setName("Java");
// 克隆 p1 對象
People p2 = (People) p1.clone();
// 打印名稱
System.out.println("p2:" + p2.getName());
}
static class People implements Cloneable {
// 屬性
private Integer id;
private String name;
/**
* 重寫 clone 方法
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
執(zhí)行結(jié)果為:
p2:Java在 java.lang.Object 中對 clone() 方法的約定有哪些?
對于所有對象來說,x.clone() !=x 應(yīng)當(dāng)返回 true,因?yàn)榭寺ο笈c原對象不是同一個(gè)對象;
對于所有對象來說,x.clone().getClass() == x.getClass() 應(yīng)當(dāng)返回 true,因?yàn)榭寺ο笈c原對象的類型是一樣的;
對于所有對象來說,x.clone().equals(x) 應(yīng)當(dāng)返回 true,因?yàn)槭褂?equals 比較時(shí),它們的值都是相同的。
Arrays.copyOf() 是深克隆還是淺克???
如果是數(shù)組類型,我們可以直接使用 Arrays.copyOf() 來實(shí)現(xiàn)克隆,實(shí)現(xiàn)代碼如下:
People[] o1 = {new People(1, "Java")};
People[] o2 = Arrays.copyOf(o1, o1.length);
// 修改原型對象的第一個(gè)元素的值
o1[0].setName("Jdk");
System.out.println("o1:" + o1[0].getName());
System.out.println("o2:" + o2[0].getName());執(zhí)行結(jié)果:
o1:Jdk
o2:Jdk從結(jié)果可以看出,我們在修改克隆對象的第一個(gè)元素之后,原型對象的第一個(gè)元素也跟著被修改了,這說明 Arrays.copyOf() 其實(shí)是一個(gè)淺克隆。
因?yàn)閿?shù)組比較特殊數(shù)組本身就是引用類型,因此在使用 Arrays.copyOf() 其實(shí)只是把引用地址復(fù)制了一份給克隆對象,如果修改了它的引用對象,那么指向它的(引用地址)所有對象都會發(fā)生改變,因此看到的結(jié)果是,修改了克隆對象的第一個(gè)元素,原型對象也跟著被修改了。
深克隆的實(shí)現(xiàn)方式有幾種?
所有對象都實(shí)現(xiàn)克隆方法;
通過構(gòu)造方法實(shí)現(xiàn)深克隆;
使用 JDK 自帶的字節(jié)流實(shí)現(xiàn)深克隆;
使用第三方工具實(shí)現(xiàn)深克隆,比如 Apache Commons Lang;
使用 JSON 工具類實(shí)現(xiàn)深克隆,比如 Gson、FastJSON 等。
具體代碼實(shí)現(xiàn)方式可以查看:
https://shimo.im/docs/L9kBMnZ56GTnDRqK/ 《深克隆和淺克隆有什么區(qū)別?它的實(shí)現(xiàn)方式有哪些?》,可復(fù)制鏈接后用石墨文檔 App 或小程序打開
Java 中的克隆為什么要設(shè)計(jì)成,既要實(shí)現(xiàn)空接口 Cloneable,還要重寫 Object 的 clone() 方法?
Java 中實(shí)現(xiàn)克隆需要兩個(gè)主要的步驟,一是 實(shí)現(xiàn) Cloneable 空接口,二是重寫 Object 的 clone() 方法再調(diào)用父類的克隆方法 (super.clone())
從源碼中可以看出 Cloneable 接口誕生的比較早,JDK 1.0 就已經(jīng)存在了,因此從那個(gè)時(shí)候就已經(jīng)有克隆方法了,那我們怎么來標(biāo)識一個(gè)類級別對象擁有克隆方法呢?克隆雖然重要,但我們不能給每個(gè)類都默認(rèn)加上克隆,這顯然是不合適的,那我們能使用的手段就只有這幾個(gè)了:
在類上新增標(biāo)識,此標(biāo)識用于聲明某個(gè)類擁有克隆的功能,像 final 關(guān)鍵字一樣;
使用 Java 中的注解;
實(shí)現(xiàn)某個(gè)接口;
繼承某個(gè)類。
第一個(gè),為了一個(gè)重要但不常用的克隆功能, 單獨(dú)新增一個(gè)類標(biāo)識,這顯然不合適;第二個(gè),因?yàn)榭寺」δ艹霈F(xiàn)的比較早,那時(shí)候還沒有注解功能,因此也不能使用;第三點(diǎn)基本滿足我們的需求,第四點(diǎn)和第一點(diǎn)比較類似,為了一個(gè)克隆功能需要犧牲一個(gè)基類,并且 Java 只能單繼承,因此這個(gè)方案也不合適。采用排除法,無疑使用實(shí)現(xiàn)接口的方式是那時(shí)最合理的方案了,而且在 Java 語言中一個(gè)類可以實(shí)現(xiàn)多個(gè)接口。
那為什么要在 Object 中添加一個(gè) clone() 方法呢?
因?yàn)?clone() 方法語義的特殊性,因此最好能有 JVM 的直接支持,既然要 JVM 直接支持,就要找一個(gè) API 來把這個(gè)方法暴露出來才行,最直接的做法就是把它放入到一個(gè)所有類的基類 Object 中,這樣所有類就可以很方便地調(diào)用到了。
