淺克隆和深克隆

一、淺克隆(ShallowClone)

在淺克隆中,如果原型對(duì)象的成員變量是值類(lèi)型,將復(fù)制一份給克隆對(duì)象;如果原型對(duì)象的成員變量是引用類(lèi)型,則將引用對(duì)象的地址復(fù)制一份給克隆對(duì)象,也就是說(shuō)原型對(duì)象和克隆對(duì)象的成員變量指向相同的內(nèi)存地址。

簡(jiǎn)單來(lái)說(shuō),在淺克隆中,當(dāng)對(duì)象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類(lèi)型的成員變量,而引用類(lèi)型的成員對(duì)象并沒(méi)有復(fù)制。

二、深克隆(DeepClone)

在深克隆中,無(wú)論原型對(duì)象的成員變量是值類(lèi)型還是引用類(lèi)型,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象。

簡(jiǎn)單來(lái)說(shuō),在深克隆中,除了對(duì)象本身被復(fù)制外,對(duì)象所包含的所有成員變量也將復(fù)制。

Java 中,如果需要實(shí)現(xiàn)深克隆,可以通過(guò)覆蓋 Object 類(lèi) 的 clone() 實(shí)現(xiàn),也可以通過(guò)序列化(Serialization)等方式來(lái)實(shí)現(xiàn)。

如果引用類(lèi)型里面還包含很多引用類(lèi)型,或者內(nèi)層引用類(lèi)型的類(lèi)里面又包含引用類(lèi)型,使用 clone 方法就會(huì)很麻煩。這時(shí)可以用序列化的方式來(lái)實(shí)現(xiàn)對(duì)象的深克隆。

序列化就是將對(duì)象寫(xiě)到流的過(guò)程,寫(xiě)到流中的對(duì)象是原有對(duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于內(nèi)存中。通過(guò)序列化實(shí)現(xiàn)的拷貝不僅可以復(fù)制對(duì)象本身,而且可以復(fù)制其引用的成員對(duì)象,因此通過(guò)序列化將對(duì)象寫(xiě)到一個(gè)流中,再?gòu)牧骼飳⑵渥x出來(lái),可以實(shí)現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對(duì)象其類(lèi)必須實(shí)現(xiàn) Serializable 接口,否則無(wú)法實(shí)現(xiàn)序列化操作。

Java 提供的 Cloneable 接口和 Serializable 接口的代碼非常簡(jiǎn)單,它們都是空接口,這種空接口也稱(chēng)為標(biāo)識(shí)接口,標(biāo)識(shí)接口中沒(méi)有任何方法的定義,其作用是告訴 JRE 這些接口的實(shí)現(xiàn)類(lèi)是否具有某個(gè)功能,如是否支持克隆、是否支持序列化等。

三、解決多層克隆問(wèn)題

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是顯式聲明ID
  public Inner inner;
 //Discription:[深度復(fù)制方法,需要對(duì)象及對(duì)象所有的對(duì)象屬性都實(shí)現(xiàn)序列化]
  public Outer myclone() {
      Outer outer = null;
      try {
//將該對(duì)象序列化成流,因?yàn)閷?xiě)在流里的是對(duì)象的一個(gè)拷貝,
//而原對(duì)象仍然存在于JVM里面。所以利用這個(gè)特性可以實(shí)現(xiàn)對(duì)象的深拷貝
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream oos = new ObjectOutputStream(baos);
          oos.writeObject(this);
      // 將流序列化成對(duì)象
          ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
          ObjectInputStream ois = new ObjectInputStream(bais);
          outer = (Outer) ois.readObject();
      } catch (IOException e) {
          e.printStackTrace();
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      return outer;
  }
}

Inner 也必須實(shí)現(xiàn) Serializable,否則無(wú)法序列化:

public class Inner implements Serializable{
  private static final long serialVersionUID = 872390113109L; //最好是顯式聲明ID
  public String name = "";
  public Inner(String name) {
      this.name = name;
  }
  @Override
  public String toString() {
      return "Inner的name值為:" + name;
  }
}

這樣也能使兩個(gè)對(duì)象在內(nèi)存空間內(nèi)完全獨(dú)立存在,互不影響對(duì)方的值。

四、理解

假如說(shuō)想復(fù)制一個(gè)簡(jiǎn)單變量。很簡(jiǎn)單:

int apples = 5;
int pears = apples;

不僅僅是 int 類(lèi)型,其它七種基礎(chǔ)數(shù)據(jù)類(lèi)型(byte、short、char、long、float、double、boolean)同樣適用于該類(lèi)情況。但是如果復(fù)制的是一個(gè)對(duì)象,情況就有些復(fù)雜了:

class Student {
    private int number;
    public int getNumber() {
        return number;
    }
    public void setNumber(int number) {
        this.number = number;
    }
}
public class Test {
    public static void main(String args[]) {
        Student stu1 = new Student();
        stu1.setNumber(12345);
        Student stu2 = stu1;
        System.out.println("學(xué)生1:" + stu1.getNumber());
        System.out.println("學(xué)生2:" + stu2.getNumber());
    }  
}

結(jié)果:
學(xué)生1:12345
學(xué)生2:12345

試著改變 stu2 實(shí)例的 number 字段,再打印結(jié)果看看:

stu2.setNumber(54321);
System.out.println("學(xué)生1:" + stu1.getNumber()); 
System.out.println("學(xué)生2:" + stu2.getNumber());

結(jié)果:
學(xué)生1:54321
學(xué)生2:54321

這就怪了,為什么改變學(xué)生 2 的學(xué)號(hào),學(xué)生 1 的學(xué)號(hào)也發(fā)生了變化呢?原因出在stu2 = stu1這一句。該句的作用是將 stu1 的引用賦值給 stu2,這樣 stu1 和 stu2 就指向內(nèi)存堆中同一個(gè)對(duì)象。如圖:

那么,怎樣才能復(fù)制一個(gè)對(duì)象呢?Object。它有 11 個(gè)方法,有兩個(gè) protected 的方法,其中一個(gè)為 clone 方法。Java 中所有的類(lèi)都是缺省的繼承 Object 類(lèi)的,查看源碼,發(fā)現(xiàn)有一個(gè) protected 的方法 clone():

/*
Creates and returns a copy of this object. The precise meaning of "copy" may 
depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;

它是一個(gè) native 方法,native 方法是非 Java 語(yǔ)言實(shí)現(xiàn)的代碼,供 Java 程序調(diào)用的,因?yàn)?Java 程序是運(yùn)行在 JVM 虛擬機(jī)上面的,要想訪(fǎng)問(wèn)到比較底層的與操作系統(tǒng)相關(guān)的就沒(méi)辦法了,只能由靠近操作系統(tǒng)的語(yǔ)言來(lái)實(shí)現(xiàn)。

  1. 第一次聲明,保證克隆對(duì)象將有單獨(dú)的內(nèi)存地址分配。
  2. 第二次表明,原始和克隆的對(duì)象應(yīng)該具有相同的類(lèi)類(lèi)型,但它不是強(qiáng)制性的。
  3. 第三次表明,原始和克隆的對(duì)象應(yīng)該是平等的 equals() 使用,但它不是強(qiáng)制性的。

因?yàn)槊總€(gè)類(lèi)直接或間接的父類(lèi)都是 Object,因此它們都含有 clone(),但是因?yàn)樵摲椒ㄊ?protected,所以都不能在類(lèi)外進(jìn)行訪(fǎng)問(wèn)。要想對(duì)一個(gè)對(duì)象進(jìn)行復(fù)制,就需要對(duì) clone 方法覆蓋。

五、為什么需要克隆對(duì)象?直接new一個(gè)對(duì)象不行嗎?

克隆的對(duì)象可能包含一些已經(jīng)修改過(guò)的屬性,而 new 出來(lái)的對(duì)象的屬性都還是初始化時(shí)候的值,所以當(dāng)需要一個(gè)新的對(duì)象來(lái)保存當(dāng)前對(duì)象的“狀態(tài)”就靠 clone 方法了。那么把這個(gè)對(duì)象的臨時(shí)屬性一個(gè)一個(gè)的賦值給新 new 的對(duì)象不也行嘛?可以,但是麻煩。況且源碼里 clone 是一個(gè) native 方法,極其方便。

Object a=new Object();
Object b;
b=a;

這種形式的代碼復(fù)制的是引用,即對(duì)象在內(nèi)存中的地址,a 和 b 對(duì)象仍然指向了同一個(gè)對(duì)象。而通過(guò) clone 方法賦值的對(duì)象跟原來(lái)的對(duì)象時(shí)同時(shí)獨(dú)立存在的。

六、如何實(shí)現(xiàn)克隆

Java中,數(shù)據(jù)類(lèi)型分為值類(lèi)型(基本數(shù)據(jù)類(lèi)型)和引用類(lèi)型。值類(lèi)型包括 byte、short、char、int、long、float、double、boolean 簡(jiǎn)單數(shù)據(jù)類(lèi)型;引用類(lèi)型包括類(lèi)、接口、數(shù)組等復(fù)雜類(lèi)型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類(lèi)型的成員變量的復(fù)制,下面將對(duì)兩者進(jìn)行詳細(xì)介紹。

1??淺克隆

  1. 被復(fù)制的類(lèi)需要實(shí)現(xiàn) Clonenable 接口(不實(shí)現(xiàn)的話(huà)在調(diào)用 clone 方法會(huì)拋出 CloneNotSupportedException),該接口為標(biāo)記接口(不含任何方法)。
  2. 覆蓋 clone(),訪(fǎng)問(wèn)修飾符設(shè)為 public。方法中調(diào)用 super.clone() 得到需要復(fù)制的對(duì)象。(native 為本地方法)

下面對(duì)上面那個(gè)方法進(jìn)行改造:

class Student implements Cloneable{  
    private int number;  
    public int getNumber() {  
        return number;  
    }  
    public void setNumber(int number) {  
        this.number = number;  
    }  
      
    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return stu;  
    }  
}  
public class Test {  
    public static void main(String args[]) {  
        Student stu1 = new Student();  
        stu1.setNumber(12345);  
        Student stu2 = (Student)stu1.clone();  
          
        System.out.println("學(xué)生1:" + stu1.getNumber());  
        System.out.println("學(xué)生2:" + stu2.getNumber());  
          
        stu2.setNumber(54321);  
      
        System.out.println("學(xué)生1:" + stu1.getNumber());  
        System.out.println("學(xué)生2:" + stu2.getNumber());  
        System.out.println(stu1 == stu2); // false
    }  
}

結(jié)果:
學(xué)生1:12345
學(xué)生2:12345
學(xué)生1:12345
學(xué)生2:54321
false -----說(shuō)明這兩個(gè)對(duì)象不是同一個(gè)對(duì)象

2??深克隆

在學(xué)生類(lèi)里再加一個(gè) Address 類(lèi)。

class Address  {
    private String add;
    public String getAdd() {
        return add;
    }
    public void setAdd(String add) {
        this.add = add;
    }
}
class Student implements Cloneable{
    private int number;
    private Address addr;
    public Address getAddr() {
        return addr;
    }
    public void setAddr(Address addr) {
        this.addr = addr;
    }
    public int getNumber() {
        return number;
    }
    public void setNumber(int number) {
        this.number = number;
    }

    @Override
    public Object clone() {
        Student stu = null;
        try{
            stu = (Student)super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return stu;
    }
}
public class Test {
    public static void main(String args[]) {
        Address addr = new Address();
        addr.setAdd("杭州市");
        Student stu1 = new Student();
        stu1.setNumber(123);
        stu1.setAddr(addr);
        Student stu2 = (Student)stu1.clone();
        System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
    }
}

結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市

在 main 方法中試著改變 addr 實(shí)例的地址:

addr.setAdd("西湖區(qū)");  
System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());

結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:西湖區(qū)

原因是淺復(fù)制只是復(fù)制了 addr 變量的引用,并沒(méi)有真正的開(kāi)辟另一塊空間,將值復(fù)制后再將引用返回給新對(duì)象。所以,為了達(dá)到真正的復(fù)制對(duì)象,而不是純粹引用復(fù)制。需要將 Address 類(lèi)可復(fù)制化,并且修改 clone 方法,完整代碼如下:

class Address implements Cloneable {
    private String add;
    public String getAdd() {
        return add;
    }
    public void setAdd(String add) {
        this.add = add;
    }

    @Override
    public Object clone() {
        Address addr = null;
        try{
            addr = (Address)super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return addr;
    }
}

class Student implements Cloneable{
    private int number;
    private Address addr;
    public Address getAddr() {
        return addr;
    }
    public void setAddr(Address addr) {
        this.addr = addr;
    }
    public int getNumber() {
        return number;
    }
    public void setNumber(int number) {
        this.number = number;
    }

    @Override
    public Object clone() {
        Student stu = null;
        try{
            stu = (Student)super.clone();   //淺復(fù)制
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        stu.addr = (Address)addr.clone();   //深度復(fù)制
        return stu;
    }
}
public class Test {
    public static void main(String args[]) {
        Address addr = new Address();
        addr.setAdd("杭州市");
        Student stu1 = new Student();
        stu1.setNumber(123);
        stu1.setAddr(addr);
        Student stu2 = (Student)stu1.clone();
        System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        addr.setAdd("西湖區(qū)");
        System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
    }
}

結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:杭州市

最后可以看看 API 里其中一個(gè)實(shí)現(xiàn)了 clone 方法的類(lèi):java.util.Date:

/** 
 * Return a copy of this object. 
 */  
public Object clone() {  
    Date d = null;  
    try {  
        d = (Date)super.clone();  
        if (cdate != null) {  
            d.cdate = (BaseCalendar.Date) cdate.clone();  
        }  
    } catch (CloneNotSupportedException e) {} // Won't happen  
    return d;  
}

該類(lèi)其實(shí)也屬于深度復(fù)制。

七、總結(jié)

實(shí)現(xiàn)對(duì)象克隆有兩種方式:
1??實(shí)現(xiàn) Cloneable 接口并重寫(xiě) Object 類(lèi)中的 clone()。
2??實(shí)現(xiàn) Serializable 接口,通過(guò)對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆。
3??注意:基于序列化和反序列化實(shí)現(xiàn)的克隆不僅僅是深度克隆,更重要的是通過(guò)泛型限定,可以檢查出要克隆的對(duì)象是否支持序列化,這項(xiàng)檢查是編譯器完成的,不是在運(yùn)行時(shí)拋出異常,這種是方案明顯優(yōu)于使用 Object 類(lèi)的 clone 方法克隆對(duì)象。讓問(wèn)題在編譯的時(shí)候暴露出來(lái)總是優(yōu)于把問(wèn)題留到運(yùn)行時(shí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容