Java - 物件複製,cloneable與序列化複製的區別
當需要對同一個類,生成多個物件時。一般有三種方法:new()、clone()、以及序列化複製
new和clone的區別,簡單的說一下:
new的操作為 分配記憶體。程式執行到new操作符時, 首先去看new操作符後面的型別,因為知道了型別,才能知道要分配多大的記憶體空間。分配完記憶體之後,再呼叫建構函式,填充物件的各個域,這一步叫做物件的初始化,構造方法返回後,一個物件建立完畢,可以把他的引用(地址)釋出到外部,在外部就可以使用這個引用操縱這個物件。
而clone在第一步是和new相似的, 都是分配記憶體,呼叫clone方法時,分配的記憶體和源物件(即呼叫clone方法的物件)相同,然後再使用原物件中對應的各個域,填充新物件的域, 填充完成之後,clone方法返回,一個新的相同的物件被建立,同樣可以把這個新物件的引用釋出到外部。
由此可見,clone的速度是大於new的,尤其是在對大物件操作時。
然而,我們知道拷貝分為深拷貝和淺拷貝。
淺拷貝:將一個物件複製後,基本資料型別的變數都會重新建立,而引用型別,指向的還是原物件所指向的。
深拷貝:將一個物件複製後,不論是基本資料型別還有引用型別,都是重新建立的。
顯然,淺拷貝是不安全的,可不是我們想要的。
下面我們來看一下clone方法和序列化方法,對於深拷貝淺拷貝的區別差異:
先看一下clone方法的淺拷貝和深拷貝。
我們先定義一個Person類,用作引用型別。
public class Person{ int i =0; }
然後在寫一個需要拷貝物件的類。TextClass。並實現Cloneable介面的clone方法。
public class TextClass implements Cloneable { private int age; private String name; final int i=9; private Person person; public TextClass clone(){ TextClass textClass = null; try { textClass =(TextClass)super.clone(); }catch (CloneNotSupportedException e) { System.out.println(e); } return textClass; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Person getPerson() { return person; } public void setPerson( Person person) { this.person = person; } }
現在,我們執行程式碼來看看前後clone物件的值,是否相等。(完整程式碼在最後一起給出,此處為了方便理解,逐語句的驗證輸出)。我們先例項化了一個text1物件,作為原物件。之後通過clone來複製出一個textClass1物件。
TextClass text1 =new TextClass();
text1.setAge(40);
text1.setName(new String("zhou"));
Person person = new Person();
text1.setPerson(person);
TextClass textClass1 = text1.clone();
System.out.println("1: person比較");
System.out.println(text1.getPerson()==textClass1.getPerson());
分析,此次是淺複製,我們沒有對Person進行進一步的拷貝,賦值。所以輸出因為該true。
輸出結果為:true,無誤。
1: person比較
true
那麼該如何實現深複製呢?畢竟深複製才是我們想要的。這就必須對引用型別進行進一步的拷貝和賦值。修改之後的程式碼為:
public class Person implements Cloneable{
int i =0;
public Person clone(){
Person person =null;
try {
person =(Person)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return person;
}
}
public class TextClass implements Cloneable
{
private int age;
private String name;
final int i=9;
private Person person;
public TextClass clone(){
TextClass textClass = null;
try {
textClass =(TextClass)super.clone();
textClass.setPerson(this.getPerson().clone()); //加入的語句
}catch (CloneNotSupportedException e) {
System.out.println(e);
}
return textClass;
}
// set get...
}
再次執行,上面的測試程式碼,如果正確,應該為false。因為此時的person已用了clone來複制,兩者指向的地址並不一樣。結果,無誤:
1: person比較
false
下面我們來看一下使用序列化的方式來複制時的情況
首先寫一個序列化方法類:
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 ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新物件
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
在進行測試之前,我們先要將Person和TextClass類實現介面 Serializable;以保證能夠序列化
public class TextClass implements Serializable,Cloneable{
...
}
public class Person implements Cloneable,Serializable{
...
}
測試程式碼為:
TextClass text2 = CloneUtils.clone(text1);
System.out.println("1: person比較");
System.out.println(text1.getPerson()==text2.getPerson());
因為序列化是將物件寫進二進位制流中,然後經過反序列化,再從中讀取出來,所以應該是完全不一樣的兩個物件。
故而是一個徹底的深複製。實驗結果因為為 false。執行也是無誤
1: person比較
false
至此,我們可以小結一下,clone方法只能複製基本型別,對於引用型別它只是淺拷貝。遇到引用型別,我們必須要對該屬性(person)進行單獨的clone。當引用型別太多時,會多出很大的工作量。也真是因為這樣,所以引用型別一旦是final修飾時,我們就不能使用clone方法了。
序列化和反序列化,則是徹底的深拷貝。
在實驗的時候,還發現clone拷貝String變數的問題。先不說是為什麼,直接上測試程式碼。
System.out.println("2: name比較");
System.out.println(text1.getName()==textClass1.getName());
text1.setName("xu");
System.out.println(text1.getName());
System.out.println(textClass1.getName());
System.out.println("3: name比較");
System.out.println(text1.getName()==textClass1.getName());
大家猜一下實驗結果是什麼?是不是想說:
2: name比較
true
zhou
zhou
xu
xu
3: name比較
true
如果,你是這麼想的。那麼說明你關於clone是懂了。但是你執行後會發現,結果不是這樣的。
哈哈哈,驚喜不,意外不。
執行的結果是這樣的:
2: name比較
true
zhou
zhou
xu
zhou
3: name比較
false
前面四個輸出是沒有什麼問題的。第一個true,是因為String為引用型別,是淺複製。
問題出現在,對text1.setName(“xu”);之後,既然是淺複製為什麼textClass1.getName()不為“xu”;
這是因為String底層存放的資料是final的!
當你執行text1.setName(“xu”)、並不是將text1.Name指向的“zhou”改成“xu”;而是生成一個新的String物件“xu”;讓text1.Name指向“xu”;而textClass1.getName()=“zhou”;也證明了這一點,原來地址上的資料並沒有被修改。
最後一個false ,想必到現在你也知道了,是的,因為text1.Name指向的地址偷偷地變了,不再是原來存放“zhou”的地址,
而是新String物件“xu”的地址。