Java進階——Java中的克隆

對象的創建

在Android中,對象的創建分爲兩種形式,一種是使用new操作符創建對象,另一種是調用clone方法複製對象

  • 使用new操作符創建對象:對new的對象分配內存,調用其構造方法,並將創建好的對象引用發佈到外部
  • 調用clone方法複製對象:對clone的對象分配內存,對新分配的內存域使用原對象進行填充

克隆的使用

在對象中可以使用clone(),必須實現Cloneable接口,複寫clone方法,外部纔可以調用clone()

public class Person implements Cloneable{

    public String name;
    public int age;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

拷貝對象和拷貝引用

一、拷貝對象

Person p1 = new Person();
Person p2 = p1;

Log.e("TAG", p1.toString());
Log.e("TAG", p2.toString());

通過輸出的結果看出,兩個對象的地址是相同的,所以這兩個對象是同一個對象,這種現象叫做拷貝對象

com.hensen.fashionsource.Person@a6ea782
com.hensen.fashionsource.Person@a6ea782

二、拷貝引用

try {
    Person p1 = new Person();
    Person p2 = (Person) p1.clone();

    Log.e("TAG", p1.toString());
    Log.e("TAG", p2.toString());
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

通過輸出的結果看出,兩個對象的地址不相同,所以這兩個對象是不同對象,這種現象叫做拷貝引用

com.hensen.fashionsource.Person@38b31e93
com.hensen.fashionsource.Person@3046fed0

淺拷貝和深拷貝

在clone()的過程中,clone會遇到淺拷貝和深拷貝的問題。本質上clone()屬於淺拷貝,但是也可以將clone()轉換成深拷貝來處理。下面是淺拷貝和深拷貝的概念介紹:

  • 淺拷貝:將對象中的所有字段複製到新的對象中。其中,基本數據類型的值被複制到對象中後,在對象中的修改不會影響到源對象對應的值。而引用類型的值被複制到對象中還是引用類型,在對象中對引用類型的字段做修改會影響到源對象本身。簡單的說,拷貝基本數據類型的值和拷貝引用類型的引用
  • 深拷貝:將對象中的所有字段複製到新的對象中。不過,無論是對象的基本數據類型,還是引用類型,都會被重新創建並賦值,對於新對象的修改,不會影響到源對象本身。簡單的說,拷貝出完全相同的對象,對新對象的修改和源對象沒有任何影響

一、淺拷貝

clone()屬於淺拷貝,那麼怎麼去驗證它呢?下面我們通過人->手->手指的嵌套關係來驗證

public class Person implements Cloneable{

    public String name;
    public int age;
    public Hand hand;

    public Person(String name, Hand hand) {
        this.name = name;
        this.hand = hand;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Hand {
    public Finger finger;
    public String name;

    public Hand(Finger finger, String name) {
        this.finger = finger;
        this.name = name;
    }
}

public class Finger {

}

當前數據結構如圖所示

這裏寫圖片描述

我們通過比較手和手指的引用類型地址是否相同,可以看出clone()的本質是淺拷貝

try {
    Person p1 = new Person("張三", new Hand(new Finger(), "A"));
    Person p2 = (Person) p1.clone();

    Log.e("TAG", "" + p1);
    Log.e("TAG", "" + p2);
    Log.e("TAG", "" + p1.hand);
    Log.e("TAG", "" + p2.hand);

    Log.e("TAG", "" + p1.hand.finger);
    Log.e("TAG", "" + p2.hand.finger);

} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

輸出結果可以看出,clone()引用類型的地址是相同的

com.hensen.fashionsource.Person@330bedb4
com.hensen.fashionsource.Person@2503dbd3
com.hensen.fashionsource.Hand@38b31e93
com.hensen.fashionsource.Hand@38b31e93
com.hensen.fashionsource.Finger@3046fed0
com.hensen.fashionsource.Finger@3046fed0

當前數據結構如圖所示

這裏寫圖片描述

三、深拷貝

1、如果我們想對Person的Hand對象進行深拷貝該怎麼做呢?可以讓Person的Hand對象具有拷貝功能,對Hand進行改造

public class Person implements Cloneable{

    public String name;
    public int age;
    public Hand hand;

    public Person(String name, Hand hand) {
        this.name = name;
        this.hand = hand;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person newPer = (Person) super.clone();
        newPer.hand = (Hand) hand.clone();
        return newPer;
    }
}

public class Hand implements Cloneable{
    public Finger finger;
    public String name;

    public Hand(Finger finger, String name) {
        this.finger = finger;
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Finger {

}

下面我們繼續輸出拷貝前p1的地址和拷貝後p2的地址,我們可以發現Hand已經完成了深拷貝

com.hensen.fashionsource.Person@330bedb4
com.hensen.fashionsource.Person@2503dbd3
com.hensen.fashionsource.Hand@38b31e93
com.hensen.fashionsource.Hand@3046fed0
com.hensen.fashionsource.Finger@14be0ec9
com.hensen.fashionsource.Finger@14be0ec9

當前數據結構如圖所示

這裏寫圖片描述

2、如果我們想對Person的Hand和Finger對象進行深拷貝該怎麼做呢?同樣重複上面的步驟,對Finder進行改造

public class Person implements Cloneable{

    public String name;
    public int age;
    public Hand hand;

    public Person(String name, Hand hand) {
        this.name = name;
        this.hand = hand;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person newPer = (Person) super.clone();
        newPer.hand = (Hand) hand.clone();
        return newPer;
    }
}

public class Hand implements Cloneable{
    public Finger finger;
    public String name;

    public Hand(Finger finger, String name) {
        this.finger = finger;
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Hand newHand = (Hand) super.clone();
        newHand.finger = (Finger) finger.clone();
        return newHand;
    }
}

public class Finger implements Cloneable{

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

下面我們繼續輸出拷貝前p1的地址和拷貝後p2的地址,我們可以發現Finger已經完成了深拷貝

com.hensen.fashionsource.Person@330bedb4
com.hensen.fashionsource.Person@2503dbd3
com.hensen.fashionsource.Hand@38b31e93
com.hensen.fashionsource.Hand@3046fed0
com.hensen.fashionsource.Finger@14be0ec9
com.hensen.fashionsource.Finger@35e3b9ce

當前數據結構如圖所示

這裏寫圖片描述

三、String類型的特殊性

由於String並不是基本數據類型,且String沒有實現Cloneable接口,在深拷貝的時候並沒有進行新地址的拷貝,僅僅只是拷貝了引用,也就是說違反了深拷貝。按照上面的理論,在深拷貝的時候,只是拷貝引用,那麼如果對String類型的值重新賦值,將會影響到源對象的值。事實是不是如此呢?答案是否定,對String類型的深拷貝將不會影響到源對象的值

原因是String類型被final修飾,在內存中是不可以被改變的對象,每次對新的字符串賦值都會分配一塊新內存,並指向它。所以在String類型進行深拷貝的時候是屬於特殊情況,但String類型在淺拷貝的時候還是屬於拷貝引用,下面還是通過剛纔的例子進行驗證

public static void main(String[] args) {
    try {
        Person p1 = new Person("張三", new Hand(new Finger(), "AAA"));
        Person p2 = (Person) p1.clone();

        p2.name = "李四";
        p2.hand.name = "BBB";

        System.out.println(p1.name.toString());
        System.out.println(p2.name.toString());

        System.out.println(p1.hand.name.toString());
        System.out.println(p2.hand.name.toString());
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}

1、當Person的Hand對象爲淺拷貝時,即在clone()中沒有做任何處理

@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();
}

其輸出的結果爲

張三
李四
BBB
BBB

分析:

  1. 由於Person本身實現了Clonable接口,對於p1.clone()屬於深拷貝,所以Person的name字段是會重新分配內存
  2. 由於Person的Hand對象並沒有在Person的clone()中進行拷貝處理,所以Person的Hand對象的name字段只是拷貝引用

2、當Person的Hand對象爲深拷貝時,即在clone()中對Hand對象進行拷貝

@Override
protected Object clone() throws CloneNotSupportedException {
    Person newPer = (Person) super.clone();
    newPer.hand = (Hand) hand.clone();
    return newPer;
}

其輸出的結果爲

張三
李四
AAA
BBB

分析:

  1. 由於Person本身實現了Clonable接口,對於p1.clone()屬於深拷貝,所以Person的name字段是會重新分配內存
  2. 由於Person的Hand對象在Person的clone()中進行拷貝處理,所以Person的Hand對象的name字段是會重新分配內存

利用序列化實現深拷貝

在內存中可以直接通過字節流的拷貝完成深拷貝,其具體步驟如下

  1. 將源對象寫入到字節流中
  2. 讀取源對象的字節流
  3. 生成新的對象
public class CloneUtils {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj){
        T cloneObj = null;
        try {
            //寫入字節流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();
            //讀取字節流
            ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(in);
            //生成新對象
            cloneObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

總結

  1. clone的使用必須實現Cloneable的接口
  2. clone屬於淺拷貝,也可以通過處理clone()實現深拷貝
  3. clone對String類型的深拷貝具有特殊性
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章