在Android(以及更廣泛的Java編程環(huán)境)中,Object類的clone()方法用于創(chuàng)建并返回對(duì)象的一個(gè)副本。然而,該方法執(zhí)行的是淺拷貝(shallow copy)而非深拷貝(deep copy)。理解這兩者的區(qū)別對(duì)于正確使用clone()方法至關(guān)重要。
淺拷貝 vs 深拷貝
-
淺拷貝(Shallow Copy)
- 當(dāng)你對(duì)一個(gè)對(duì)象執(zhí)行淺拷貝時(shí),會(huì)創(chuàng)建一個(gè)新的對(duì)象,這個(gè)新對(duì)象有著與原對(duì)象相同的實(shí)例變量值。也就是說,基本數(shù)據(jù)類型的成員變量會(huì)被復(fù)制其值,而引用類型的成員變量則只是復(fù)制了引用地址,并沒有為這些引用類型創(chuàng)建新的實(shí)例。因此,原始對(duì)象和副本對(duì)象中的引用類型成員變量指向的是同一個(gè)內(nèi)存地址上的對(duì)象。
-
深拷貝(Deep Copy)
- 相比之下,深拷貝不僅會(huì)創(chuàng)建一個(gè)新的對(duì)象并復(fù)制所有基本數(shù)據(jù)類型的成員變量值,還會(huì)遞歸地復(fù)制所有引用類型的成員變量,使得原始對(duì)象和副本對(duì)象中的引用類型成員變量指向不同的對(duì)象。這意味著,即使兩個(gè)對(duì)象包含相同的內(nèi)容,它們之間也不會(huì)共享任何引用對(duì)象的狀態(tài)。
示例說明
假設(shè)我們有一個(gè)簡(jiǎn)單的類Person,它包含一個(gè)基本類型字段和一個(gè)引用類型字段:
class Person implements Cloneable {
private String name;
private Address address; // 假設(shè)這是一個(gè)自定義類
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// getter and setter methods...
}
class Address {
private String city;
public Address(String city) {
this.city = city;
}
// getter and setter methods...
}
如果我們對(duì)Person對(duì)象進(jìn)行淺拷貝:
Address addr = new Address("Beijing");
Person original = new Person("John", addr);
Person cloned = (Person) original.clone();
// 修改克隆對(duì)象的引用類型成員變量
cloned.getAddress().setCity("Shanghai");
System.out.println(original.getAddress().getCity()); // 輸出: Shanghai
你會(huì)發(fā)現(xiàn)修改克隆對(duì)象cloned中的address屬性也影響到了原始對(duì)象original,因?yàn)閮烧邔?shí)際上共享同一個(gè)Address對(duì)象實(shí)例。
為了實(shí)現(xiàn)深拷貝,你需要重寫clone()方法,并確保所有引用類型的成員變量也被適當(dāng)?shù)貜?fù)制:
@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = (Address) this.address.clone(); // 假設(shè)Address類也實(shí)現(xiàn)了Cloneable接口
return cloned;
}
同時(shí),Address類也需要支持克?。?/p>
class Address implements Cloneable {
private String city;
public Address(String city) {
this.city = city;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// getter and setter methods...
}
這樣,當(dāng)你再次嘗試修改克隆對(duì)象的address屬性時(shí),不會(huì)影響到原始對(duì)象,因?yàn)楝F(xiàn)在每個(gè)對(duì)象都有自己的獨(dú)立Address實(shí)例。
總結(jié)
- 淺拷貝:只復(fù)制對(duì)象的基本數(shù)據(jù)類型成員變量的值;對(duì)于引用類型的成員變量,僅復(fù)制引用地址,不創(chuàng)建新的對(duì)象實(shí)例。
- 深拷貝:不僅復(fù)制基本數(shù)據(jù)類型成員變量的值,還遞歸地復(fù)制所有引用類型的成員變量,確保每個(gè)對(duì)象都擁有獨(dú)立的引用對(duì)象實(shí)例。
理解這一點(diǎn)有助于避免因?qū)ο箝g意外共享狀態(tài)而導(dǎo)致的潛在問題。如果你的應(yīng)用場(chǎng)景需要完全獨(dú)立的對(duì)象副本,則可能需要實(shí)現(xiàn)深拷貝。
如果成員變量是int[]數(shù)組呢?會(huì)被深拷貝還是淺拷貝?
? 結(jié)論先說:
對(duì)于
int[]這種基本類型數(shù)組,clone()會(huì)創(chuàng)建一個(gè)新的數(shù)組對(duì)象,并把原數(shù)組的每個(gè)元素復(fù)制過去,所以內(nèi)容是獨(dú)立的。
從效果上看,對(duì)int[]的clone()行為類似于“深拷貝”,但嚴(yán)格來說,它仍然是淺拷貝機(jī)制的一部分,只是對(duì)數(shù)組類型有特殊處理。
?? 原理分析
Java 中 Object.clone() 對(duì)不同類型的成員變量處理方式如下:
| 成員變量類型 | clone() 行為 |
|---|---|
| 基本數(shù)據(jù)類型(int, boolean, long 等) | 直接復(fù)制值 |
基本類型數(shù)組(int[], byte[], boolean[] 等) |
創(chuàng)建新數(shù)組,并復(fù)制每個(gè)元素(值復(fù)制) |
引用類型(如 String, Object) |
復(fù)制引用(即兩個(gè)對(duì)象指向同一個(gè)對(duì)象)→ 典型淺拷貝 |
引用類型數(shù)組(如 String[], Person[]) |
創(chuàng)建新數(shù)組,但數(shù)組中的每個(gè)元素是引用復(fù)制(不是對(duì)象復(fù)制)→ 淺拷貝 |
? 示例:int[] 數(shù)組的 clone() 行為
class Data implements Cloneable {
public int[] numbers;
public Data(int[] numbers) {
this.numbers = numbers;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 使用示例
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
int[] arr = {1, 2, 3, 4};
Data data1 = new Data(arr);
Data data2 = (Data) data1.clone();
// 修改克隆對(duì)象的數(shù)組
data2.numbers[0] = 999;
System.out.println("data1.numbers[0] = " + data1.numbers[0]); // 輸出:1
System.out.println("data2.numbers[0] = " + data2.numbers[0]); // 輸出:999
}
}
?? 輸出結(jié)果:
data1.numbers[0] = 1
data2.numbers[0] = 999
? 說明:data1 和 data2 的 numbers 是兩個(gè)不同的數(shù)組對(duì)象,修改一個(gè)不會(huì)影響另一個(gè)。
? 為什么 int[] 的 clone() 是“值復(fù)制”?
因?yàn)?int[] 是基本類型數(shù)組,數(shù)組中的每個(gè)元素都是 int 值(不是對(duì)象引用),所以 clone() 時(shí)會(huì)創(chuàng)建一個(gè)新數(shù)組,并把原數(shù)組的每個(gè) int 值復(fù)制過去。
這相當(dāng)于:
int[] newArr = new int[oldArr.length];
for (int i = 0; i < oldArr.length; i++) {
newArr[i] = oldArr[i]; // 復(fù)制的是值
}
所以它是“安全的”,不會(huì)出現(xiàn)共享數(shù)據(jù)的問題。
? 總結(jié)
| 情況 | clone() 行為 | 是否獨(dú)立 |
|---|---|---|
int x |
值復(fù)制 | ? 獨(dú)立 |
int[] arr |
創(chuàng)建新數(shù)組,復(fù)制每個(gè) int 值 |
? 獨(dú)立(效果上像深拷貝) |
String s |
引用復(fù)制 | ?? 共享對(duì)象(但 String 不可變,通常安全) |
Person obj |
引用復(fù)制 | ? 共享對(duì)象 |
Person[] arr |
創(chuàng)建新數(shù)組,但每個(gè)元素是引用復(fù)制 | ?? 數(shù)組獨(dú)立,但元素對(duì)象共享 |
? 最佳實(shí)踐建議
- 如果類中包含數(shù)組(尤其是可變對(duì)象數(shù)組),不要依賴默認(rèn)的
clone()。 - 如果需要真正的“深拷貝”,應(yīng)手動(dòng)重寫
clone(),并對(duì)引用類型和引用數(shù)組進(jìn)行深度復(fù)制。 - 對(duì)于
int[],默認(rèn)clone()通常是安全的,可以放心使用。
?? 一句話總結(jié):
int[]數(shù)組在clone()時(shí)會(huì)被復(fù)制成一個(gè)新的數(shù)組對(duì)象,且每個(gè)int元素的值也被復(fù)制,因此不會(huì)與原數(shù)組共享數(shù)據(jù),行為上等價(jià)于“深拷貝”,是安全的。但這仍是 Java 淺拷貝機(jī)制對(duì)基本類型數(shù)組的特殊處理。