淺談Java中的深克隆和淺克隆(阿里面試)


在最近的秋招中,阿里和多益網絡都問到了這個問題,雖然很簡單,但是我還是想總結一下,感興趣的可以看一下我的個人博客網站(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 {
      Son result = (Son) super.clone();
	if (son != null) {
		result.son = (Son) son.clone();
	}
    return result;
}

說幾句廢話

個人認為,在選擇深克隆方法時,應根據對象的復雜程度,如引用類型屬性是否有多層引用類型屬性關系。如果對象只有一層或者兩層引用類型的屬性,選擇思考中所提到的方法較為方便,反之則使用對象流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM