在最近的秋招中,阿裡和多益網路都問到了這個問題,雖然很簡單,但是我還是想總結一下,感興趣的可以看一下我的 "個人博客網站(Spring+MyBatis+redis+nginx+mysql)" (適合菜鳥),最近會抽空把最近面試遇到的問題總結一下。 本文針對問題:深克隆和淺克隆的區別和實現方式?(阿裡 ...
在最近的秋招中,阿裡和多益網路都問到了這個問題,雖然很簡單,但是我還是想總結一下,感興趣的可以看一下我的個人博客網站(Spring+MyBatis+redis+nginx+mysql)(適合菜鳥),最近會抽空把最近面試遇到的問題總結一下。
本文針對問題:深克隆和淺克隆的區別和實現方式?(阿裡電面,多益網路的選擇題)
Talk is cheap
最近不止一次遇見深淺克隆(深複製,淺複製)的問題,除了印象中有個clone方法外一臉懵逼!!!克隆(複製)在Java中是一種常見的操作,目的是快速獲取一個對象副本。克隆分為深克隆和淺克隆。
淺克隆:創建一個新對象,新對象的屬性和原來對象完全相同,對於非基本類型屬性,仍指向原有屬性所指向的對象的記憶體地址。
深克隆:創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。
總之深淺克隆都會在堆中新分配一塊區域,區別在於對象屬性引用的對象是否需要進行克隆(遞歸性的)。
Show you my picture
pos:當前對象的地址;
son:son屬性所指向的地址;
name:對象的name屬性。
Show you my code
case1:
public class Son implements Serializable , Cloneable{
private String name;
private Son son;
public Son() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
@Override
public String toString() {
return super.toString();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
測試
public static void main(String[] args) throws Exception{
// 創建父親(LiLiu),兒子(LiWu),孫子(LiLiu)並關聯
Son father = new Son();
father.setName("LiSi");
Son son = new Son();
son.setName("LiWu");
Son grandSon = new Son();
grandSon.setName("LiLiu");
father.setSon(son);
son.setSon(grandSon);
// 調用clone方法
Son fatherCopy = (Son) father.clone();
boolean flag1 = fatherCopy==father;
boolean flag2 = fatherCopy.getSon() == son;
boolean flag3 = fatherCopy.getSon().getSon() == grandSon;
// 比較克隆後的地址
System.out.println(flag1);// false
System.out.println(flag2);// true
System.out.println(flag3);// true
// 比較Name
flag1= fatherCopy.getName()==father.getName();
flag2 = fatherCopy.getSon().getName() == son.getName();
flag3 = fatherCopy.getSon().getSon().getName() == grandSon.getName();
System.out.println(flag1);// true
System.out.println(flag2);// true
System.out.println(flag3);// true
//將對象寫到流里
ByteArrayOutputStream byteOut=new ByteArrayOutputStream();
ObjectOutputStream objOut=new ObjectOutputStream(byteOut);
objOut.writeObject(father);
//從流里讀出來
ByteArrayInputStream byteIn=new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objInput=new ObjectInputStream(byteIn);
fatherCopy = (Son) objInput.readObject();
flag1= fatherCopy==father;
flag2 = fatherCopy.getSon() == son;
flag3 = fatherCopy.getSon().getSon() == grandSon;
System.out.println(flag1);// false
System.out.println(flag2);// false
System.out.println(flag3);// false
// 比較Name
flag1= fatherCopy.getName()==father.getName();
flag2 = fatherCopy.getSon().getName() == son.getName();
flag3 = fatherCopy.getSon().getSon().getName() == grandSon.getName();
System.out.println(flag1);// false
System.out.println(flag2);// false
System.out.println(flag3);// false
}
從上文代碼及運行結果不難看出,如果對象實現Cloneable並重寫clone方法不進行任何操作時,調用clone是進行的淺克隆。而使用對象流將對象寫入流然後再讀出是進行的深克隆。
思考:既然實現Cloneable介面並重寫clone介面只能進行淺克隆。但是如果類的引用類型屬性(以及屬性的引用類型屬性)都進行淺克隆,直到沒有引用類型屬性或者引用類型屬性為null時,整體上就形成了深克隆。既對象的引用類型屬性和屬性的應用類型屬性都實現Coloneable,重寫clone方法併在clone方法中進行調用。
protected Object clone() throws CloneNotSupportedException {
if (son != null) {
this.son = (Son) son.clone();
}
return super.clone();
}
值得一提的是String比較特殊,它既不是基本數據類型,也沒有實現Cloneable介面,所以採用此種方法複製後仍然不能進行深複製。我試圖嘗試採用下文方法進行賦值,但是輸出結果仍然是和淺複製相同。(困惑,求解答)
protected Object clone() throws CloneNotSupportedException {
if (son != null) {
this.son = (Son) son.clone();
}
if (string !=null ){
this.name = new String(name);
}
return super.clone();
}
但是這似乎並不影響,因為String是final類型的,每次修改都是創建一個新的對象,所以修改克隆的副本對象並不會影響原對象。
說幾句廢話
個人認為,在選擇深克隆方法時,應根據對象的複雜成都,如引用類型屬性是否有多層引用類型屬性關係。如果對象只有一層或者兩層引用類型的屬性,選擇思考中所提到的方法較為方便,反之則使用對象流。