Java 的深拷貝和淺拷貝

基本知識

Java在運行時的內存使用分兩塊:棧內存與堆內存。

只就變量而言,棧內存上分配一些基本類型的變量(如intboolean)與對象的引用,而堆內存分配給真正的對象本身以及數組等,堆內存上的數據由棧內存上的相應變量引用,相當於棧中存儲着堆內存中實際對象或數組的標記或別名(實際上是堆內存變量首地址)。

什麼是拷貝

將對象複製出一份的行爲稱爲對象的拷貝。

一般來說,拷貝出的對象需要滿足以下三點:

  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x)

首先定義三個類。

PersonalInfo.java

      
      
1
2
3
4
5
6
      
      
public class PersonalInfo implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

Manager.java

      
      
1
2
3
4
5
6
7
8
9
10
      
      
public class Manager implements Cloneable {
private PersonalInfo personalInfo;
/* 省略 constructor 與 accessors */
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

Department.java

      
      
1
2
3
4
5
6
7
8
9
10
11
      
      
public class Department implements Cloneable {
private int empCount;
private Manager manager;
/* 省略 constructor 與 accessors */
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

當需要被克隆的對象的類沒有實現Cloneable接口而被調用clone方法時,就會拋出CloneNotSupportedException

再在Main類中編寫測試用的斷言,本文將面向測試一步一步地實現最終的徹底深拷貝

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
      
      
Department dep0 = new Department( 100, new Manager( new PersonalInfo()));
Department dep1;
// do something here
/* 是淺拷貝 */
assert dep0 != dep1;
/* 是深拷貝 */
assert dep0.getManager() != dep1.getManager();
/* 是徹底深拷貝 */
assert dep0.getManager().getPersonalInfo() != dep1.getManager().getPersonalInfo();

爲了使斷言機制工作,我們在運行/調試配置中傳入VM參數-enableassertions

主要有兩種約定俗成的形式來實現拷貝,本文選擇clone()

clone方法

Object.class

      
      
1
2
3
      
      
// ...
protected native Object clone() throws CloneNotSupportedException;
// ...

protected表示該方法只能在本身、本包以及子類中使用。

new關鍵字

這種方式與重寫clone()大同小異,唯一不同的是new關鍵字直接開闢了新對象,繼而只需要完成相應字段的拷貝工作。

傳引用

在學習編寫Java代碼的過程中,最常見的問題就是String的「相等」,以此爲思路,首先嚐試:

      
      
1
      
      
dep1 = dep0;

斷言失敗在assert dep0 != dep1;,說明dep0dep1根本就是引用了堆上的同一個對象,拷貝也就更無從談起了。

同一引用

同一引用

淺拷貝

在沒有顯式重寫Department類的clone方法時,嘗試:

      
      
1
      
      
dep1 = (Department) dep0.clone();

斷言失敗在assert dep0.getManager() != dep1.getManager();,說明

相關文章
相關標籤/搜索