深拷貝和淺拷貝的概念,我自己在學習Java的時候也沒注意,雖然Java中對象回收工作由GC幫我們做了,但在碼代碼時如果不注意也會埋下隱藏的BUG,今天我們深入探究一下深拷貝和淺拷貝。
我們在寫代碼時經常會需要將一個對象傳遞給另一個對象,Java語言中對于基本型變量采用的是值傳遞,而對于非基本類型對象傳遞時采用的引用傳遞也就是地址傳遞,而很多時候對于非基本類型對象傳遞我們也希望能夠象值傳遞一樣,使得傳遞之前和之后有不同的內存地址。在兩種情況就是我們今天要討論的 淺拷貝 和 深拷貝 。
有Java基礎的同學可能發現上面論述有些不嚴謹,String 類型在傳遞時其實也是值傳遞,因為 String 類型是不可變對象。
淺拷貝
被復制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺復制僅僅復制所考慮的對象,而不復制它所引用的對象。
下面我們看一個例子:
public class Book implements Cloneable {
String bookName;
double price;
Person author;
public Book(String bn, double price, Person author) {
bookName = bn;
this.price = price;
this.author = author;
}
public Object clone() {
Book b = null;
try {
b = (Book) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return b;
}
public static void main(String args[]) {
Person p = new Person("Dream", 34);
Book book1 = new Book("Java開發", 30.00, p);
Book book2 = (Book) b1.clone();
book2.price = 44.00;
book2.author.setAge(45);
book2.author.setName("Fish");
book2.bookName = "Android開發";
System.out.print("age = " + book1.author.getAge() + " name = "
+ book1.bookName + " price = " + book1.price);
System.out.println();
System.out.print("age = " + book2.author.getAge() + " name = "
+ book2.bookName + " price = " + book2.price);
}
}
結果:
age = 45 name = Java開發 price = 30.0
age = 45 name = Android開發 price = 44.0
從結果中發現在改變 book2 對象的 name 和 price 屬性時 book1 的屬性并不會跟隨改變,當改變 book2 對象的 author 屬性時 book1 的 author 對象的屬性也改變了,說明 author 是淺拷貝,和 book1 的 author 是使用同一引用。這時我們就需要使用深拷貝了。
深拷貝
被復制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被復制過的新對象,而不再是原有的那些被引用的對象。換言之,深復制把要復制的對象所引用的對象都復制了一遍。
為了解決上面 Person 對象未完全拷貝問題,我們需要用到深拷貝,其實很簡單在拷貝book對象的時候加入如下語句:
b.author =(Person)author.clone(); //將Person對象進行拷貝,Person對象需進行了拷貝
結果
age = 34 name = Java開發 price = 30.0
age = 45 name = Android開發 price = 44.0
上面是用 clone() 方法實現深拷貝,傳統重載clone()方法,但當類中有很多引用時,比較麻煩。 當然我們還有一種深拷貝方法,就是將對象 序列化 。
把對象寫到流里的過程是序列化(Serilization)過程;而把對象從流中讀出來的反序列化(Deserialization)過程。應當指出的是,寫在流里的是對象的一個拷貝,而原對象仍然存在于JVM里面。
在Java語言里深復制一個對象,常??梢韵仁箤ο髮崿FSerializable接口,然后把對象(實際上只是對象的一個拷貝)寫到一個流里,再從流里讀出來,便可以重建對象。
還是上面的例子,我們重寫 clone() 方法:
public Object deepClone() throws IOException, OptionalDataException, ClassNotFoundException {
// 將對象寫到流里
OutputStream bo = new ByteArrayOutputStream();
//OutputStream op = new ObjectOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
// 從流里讀出來
InputStream bi = new ByteArrayInputStream(((ByteArrayOutputStream) bo).toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
然后在拷貝對象時調用重寫的 deepClone() 方法
Book book2 = (Book) b1.deepClone();
結果
age = 34 name = Java開發 price = 30.0
age = 45 name = Android開發 price = 44.0
PS:這樣做的前提是對象以及對象內部所有引用到的對象都是可串行化的,否則,就需要仔細考察那些不可串行化的對象可否設成transient(自行了解)