對象的創建
在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
分析:
- 由於Person本身實現了Clonable接口,對於
p1.clone()
屬於深拷貝,所以Person的name字段是會重新分配內存 - 由於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
分析:
- 由於Person本身實現了Clonable接口,對於
p1.clone()
屬於深拷貝,所以Person的name字段是會重新分配內存 - 由於Person的Hand對象在Person的clone()中進行拷貝處理,所以Person的Hand對象的name字段是會重新分配內存
利用序列化實現深拷貝
在內存中可以直接通過字節流的拷貝完成深拷貝,其具體步驟如下
- 將源對象寫入到字節流中
- 讀取源對象的字節流
- 生成新的對象
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;
}
}
總結
- clone的使用必須實現Cloneable的接口
- clone屬於淺拷貝,也可以通過處理clone()實現深拷貝
- clone對String類型的深拷貝具有特殊性