原創文章,轉載請标注出處:https://www.cnblogs.com/V1haoge/p/10755377.html
一、概述
Java中的拷貝功能是由Object類的clone方法定義的。
public class Object{
//...
protected native Object clone() throws CloneNotSupportedException;
//...
}
這是一個可被重寫的本地方法。
這個方法是與Cloneable接口相關聯的,如果針對一個沒有實作Cloneable接口的方法執行器clone方法,會抛出CloneNotSupportedException異常。
二、Cloneable
Cloneable接口就是一個标記接口,表示實作了該接口的類具備了可以克隆的功能。
一般來說,如果一個類實作了該接口,那麼同時需要重寫Object中的clone方法。
這裡要謹記一點:clone方法是Object中定義的方法,而不是Cloneable中定義的,後者隻是一個标記接口,其中沒有任何方法定義。
三、clone()
當一個類實作了Cloneable之後,具備了可被克隆的基礎條件,下面就需要具體完善克隆工作的細節了。
細節當然需要依靠重寫clone方法來實作。
到這裡就涉及到了本文的重點:淺拷貝與深拷貝。
簡單說下二者的差別:
淺拷貝就是克隆出來的新對象與原對象完全一緻無二,包括引用類型字段的值。這就會有一個現象,針對引用類型的字段,兩個對象的引用位址一緻,如此一來新舊對象之間強關聯,修改其中一個對象的内容,極可能影響到另外一個的内容。
深拷貝是針對淺拷貝而言的,那就是将所有引用的對象同樣做深拷貝,這樣一來,新舊對象就不再強關聯,當我們修改其中一個對象時,不會影響到另外一個。
3.1 淺拷貝
實作淺拷貝非常簡單,直接調用Object中定義的clone方法即可。也即,Object中定義的clone方法實作的是淺拷貝功能。
讓我們來看看下面的例子:
class A {
int i = 20;
A(){}
A(int i){
this.i = i;
}
}
class B implements Cloneable{
private int i = 10;
private A a = new A(11);
public Object clone () {
B b = null;
try {
b = (B)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return b;
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
public class CloneTest {
public static void main(String[] args) {
B b1 = new B();
// 執行淺拷貝
System.out.println("執行淺拷貝:");
B cb1 = (B)b.clone();
System.out.println(cb1.equals(b));// false-表示clone的對象與原對象不同
System.out.println((cb1.getA()).equals(b.getA()));// true-表示淺拷貝引用對象不變
}
}
執行結果為:
執行淺拷貝:
false
true
通過執行個體我們可以看出,要寫實作淺拷貝需要如下幾步:
- 被拷貝對象的類實作Cloneable接口
- 被拷貝對象的類重寫Object的clone方法
- 執行拷貝操作,就是調用重寫的clone方法來完成克隆
從執行個體的執行結果中可以看出新的clone對象執行個體cb1與原對象執行個體b1,之間equals不等,這裡的equals比較的是兩個對象的位址,與“==”效果一緻。而cb1的a字段指向的A執行個體與b1的a字段指向的A執行個體卻是一樣的,這就是淺拷貝。
淺拷貝是淺層拷貝,隻在意目标對象一個,與其關聯的其他對象一概不管。
但是在實際使用中,使用淺拷貝的情況很少,多數情況下,我們需要的是一個完全嶄新的對象,包括它的字段引用,那麼我們就需要實作深拷貝了。
3.2 深拷貝
所謂深拷貝就是相對淺拷貝而言的,在淺拷貝的基礎上進一步進行深層拷貝即可,那麼具體實作呢?
class A implements Cloneable{
int i = 20;
A(){}
A(int i){
this.i = i;
}
public Object clone (){
A a = null;
try {
a = (A)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return a;
}
}
class B implements Cloneable{
private int i = 10;
private A a = new A(11);
public Object clone () {
B b = null;
try {
b = (B)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return b;
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
public class CloneTest {
public static void main(String[] args) {
B b = new B();
// 執行淺拷貝
System.out.println("執行淺拷貝:");
B cb1 = (B)b.clone();
System.out.println(cb1.equals(b));// false-表示clone的對象與原對象不同
System.out.println((cb1.getA()).equals(b.getA()));// true-表示淺拷貝引用對象不變
// 執行深拷貝
System.out.println("----------------");
System.out.println("執行深拷貝:");
B cb2 = (B)b.clone();
A ca = (A)b.getA().clone();
cb2.setA(ca);
System.out.println(cb2.equals(b));// false-表示clone的對象與原對象不同
System.out.println((cb2.getA()).equals(b.getA()));// true-表示淺拷貝引用對象不變
}
}
執行結果:
執行淺拷貝:
false
true
----------------
執行深拷貝:
false
false
通過執行個體我們可以清楚看出,要想實作深拷貝需要如下幾步:
- 被拷貝的目标對象的類的依賴對象的類A需要實作Cloneable接口
- 類A需要重寫Object的clone方法
- 在b拷貝完成後,要繼續執行b中被依賴對象a的拷貝,并将拷貝結果指派給b的拷貝對象。
可見深拷貝其實就是一個遞歸拷貝的過程,将目标對象的類依賴的所有對象的類全部實作Cloneable接口并重寫clone方法進行二層拷貝,然後是依賴對象的依賴對象...層層深入拷貝。
四、注意事項
Java中提供的類中存在一些無法被clone的類,比如StringBuffer,它既沒有實作Cloneable接口,也沒有重寫clone方法,那麼它就無法執行克隆操作,那麼遇到它該怎麼辦呢?
這裡提供一種辦法,既然是新對象,那麼直接建立一個新的StringBuffer對象,将原來的值指派進來,就等于是完成了克隆,雖然這種方式要比Object中的clone方法慢許多,但不失為一種補救措施。
還有一些類型如基本類型的裝箱類型、String,它們雖然也是引用類型,但是它們是final修飾的,且沒有實作Cloneable接口,沒有重寫clone方法,要克隆也需要采用上面所示的方式來實作。
參考:
- java Clone使用方法詳解