java中的拷貝是什麼 ?就是用Object中的clone()拷貝一個對象
在運作時刻,Object中的clone()識别出你要複制的是哪一個對象,然後為此對象配置設定空間,并進行對象的複制,将原始對象的内容一一複制到新對象的存儲空間中。
那請問new建立一個對象和clone複制一個對象有什麼差別嗎?
new操作符的本意是配置設定記憶體。程式執行到new操作符時, 首先去看new操作符後面的類型,因為知道了類型,才能知道要配置設定多大的記憶體空間。配置設定完記憶體之後,再調用構造函數,填充對象的各個域,這一步叫做對象的初始化,構造方法傳回後,一個對象建立完畢,可以把他的引用(位址)釋出到外部,在外部就可以使用這個引用操縱這個對象。而clone在第一步是和new相似的, 都是配置設定記憶體,調用clone方法時,配置設定的記憶體和源對象(即調用clone方法的對象)相同,然後再使用原對象中對應的各個域,填充新對象的域, 填充完成之後,clone方法傳回,一個新的相同的對象被建立,同樣可以把這個新對象的引用釋出到外部。
簡單舉一個栗子吧:
package fuxi;
class Student{
int age;
String name;
public Student(String name,int age) {
this.name=name;
this.age=age;
}
}
public class One {
public static void main(String args[]) {
Student student1=new Student("小蔣", 21);
Student student2=student1;
System.out.println("學生1位址"+student1);
System.out.println("學生2位址"+student2);
}
}
輸出:
意思就是說 如果是引用 直接複制的話 隻是把引用給複制一遍 意思就是 student1和student2的引用值是相同的 代表同一個對象。
淺拷貝:
淺拷貝(淺複制、淺克隆):被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。
MainObject1是一個對象 而MainObject2是MainObject1的淺克隆對象
Field1 Field2 是int ContainObject1是對象
如果是int之類的基本類型 則是直接指派代表兩個不同的變量 但是内容相同 操作一個對另一個不影響
如果是引用類型如對象之類的 那麼操作MainObject1的ContainObject1就會影響MainObject2中的ContainObject2 因為他倆引用是指向同一個對象
注意:String是特殊情況 String 類型在傳遞時其實也是值傳遞,因為 String 類型是不可變對象。 是以如果ContainObject1是String類型 那麼改變MainObject1的ContainObject1不會影響MainObject2中的ContainObject2 而是指向新的String值 具體的可以看
為什麼String是不可變的栗子:
package fuxi;
class Person {
int age;
String name;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
public void setAge(int a) {
age=a;
}
public void setName(String name) {
this.name=name;
}
}
public class One implements Cloneable {
String bookName;
double price;
Person author;
public One(String bn, double price, Person author) {
bookName = bn;
this.price = price;
this.author = author;
}
public Object clone() {
Object b = null;
try {
b = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return (One)b;
}
public static void main(String args[]) {
Person p = new Person("Dream", 34);
One book1 = new One("Java開發", 30.00, p);
One book2 = (One) book1.clone();
book2.price = 44.00;
book2.author.setAge(45);
book2.author.setName("Fish");
book2.bookName = "Android開發";
System.out.println("age = " + book1.author.age + " name = " + book1.bookName + " price = " + book1.price);
System.out.print("age = " + book2.author.age + " 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 是使用同一引用。
1.為了擷取對象的一份拷貝,我們可以利用Object類的clone()方法。
2.在派生類中覆寫基類的clone()方法,并聲明為public。
(Object類中的clone()方法是protected的)。在子類重寫的時候,可以擴大通路修飾符的範圍
3.在派生類的clone()方法中,調用super.clone()。
因為在運作時刻,Object類中的clone()識别出你要複制的是哪一個對象,然後為此對象配置設定空間
并進行對象的複制,将原始對象的内容一一複制到新對象的存儲空間中。
4.在派生類中實作Cloneable接口。這個接口中沒有什麼方法,隻是說明作用。
注意:繼承自java.lang.Object類的clone()方法是淺複制。
clone()方法是複制對象的最快方法。這個過程需要更少的步驟來完成。
預設情況下,clone()方法提供對象的淺表副本; 即,如果我們調用super.clone(),那麼它是一個淺拷貝。
clone()方法将對象複制了一份并傳回給調用者。
①對任何的對象x,都有x.clone() != x;//克隆對象與原對象不是同一個對象
②對任何的對象x,都有x.clone().getClass()== x.getClass();//克隆對象與原對象的類型一樣
總結 :淺拷貝 :淺拷貝對象要繼承Cloneable 然後重寫clone 直接傳回(對象)super.clone即可
深拷貝:
深拷貝(深複制、深克隆):被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。
那些引用其他對象的變量将指向被複制過的新對象,而不再是原有的那些被引用的對象。
換言之,深拷貝把要複制的對象所引用的對象都複制了一遍。
直接看圖 是全新的 對象 深拷貝後
源對象和拷貝對象之間互不影響。無論字段是否是應用類型還是原始資料類型。
那麼如何實作深拷貝呢?
其實 很簡單:
即 原來的person淺拷貝處理:
class Person implements Cloneable{
int age;
String name;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
public void setAge(int a) {
age=a;
}
public void setName(String name) {
this.name=name;
}
public Object clone() {
Object b = null;
try {
b = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return (Person)b;
}
}
加上這句話就可以了
book2.author =(Person)author.clone(); //将Person對象進行拷貝,Person對象需進行了拷貝
即:
One book2 = (One) book1.clone();
book2.author=(Person)p.clone();
上面是用 clone() 方法實作深拷貝,傳統重載clone()方法,但當類中有很多引用時,比較麻煩。 當然我們還有一種深拷貝方法,就是将對象 序列化 。
把對象寫到流裡的過程是序列化(Serilization)過程;而把對象從流中讀出來的反序列化(Deserialization)過程。應當指出的是,寫在流裡的是對象的一個拷貝,而原對象仍然存在于JVM裡面。
在Java語言裡深複制一個對象,常常可以先使對象實作Serializable接口,然後把對象(實際上隻是對象的一個拷貝)寫到一個流裡,再從流裡讀出來,便可以重建對象。
public Object deepClone()
{
//将對象寫到流裡
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//從流裡讀出來
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
注意: 如果拷貝類裡面有類對象的話 那麼那個類也得是實作Serializable接口
然後拷貝對象的時候調用deepClone()方法 來代替clone方法
One book2 =(One)book1.deepClone();
完整代碼如下 : 不難 耐心看完
package fuxi;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.OutputStream;
import java.io.Serializable;
class Person implements Serializable {
int age;
String name;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
public void setAge(int a) {
age=a;
}
public void setName(String name) {
this.name=name;
}
}
public class One implements Serializable {
String bookName;
double price;
Person author;
public One(String bn, double price, Person author) {
bookName = bn;
this.price = price;
this.author = author;
}
public Object deepClone() throws IOException, OptionalDataException, ClassNotFoundException {
// 将對象寫到流裡
OutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
// 從流裡讀出來
InputStream bi = new ByteArrayInputStream(((ByteArrayOutputStream) bo).toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
public static void main(String args[]) throws OptionalDataException, ClassNotFoundException, IOException {
Person p = new Person("Dream", 34);
One book1 = new One("Java開發", 30.00, p);
One book2 =(One)book1.deepClone();
book2.price = 44.00;
book2.author.setAge(45);
book2.author.setName("Fish");
book2.bookName = "Android開發";
System.out.println("age = " + book1.author.age + " name = " + book1.bookName + " price = " + book1.price);
System.out.print("age = " + book2.author.age + " name = " + book2.bookName + " price = " + book2.price);
}
}
輸出是:
age = 34 name = Java開發 price = 30.0
age = 45 name = Android開發 price = 44.0
總結:
淺拷貝:
深拷貝:
淺拷貝對象需要繼承Cloneable接口 重寫clone 傳回super.clone 并且把protect改為public 依靠clone實作拷貝
調用: Student s2 = (Student)s1.clone();即可
深拷貝對象需要繼承Serializable接口 依靠序列化 自定義方法 來寫入讀出流
調用:One book2 =(One)book1.deepClone();
注意:序列化的時候考慮transient 因為這個修飾符可以 使被修飾的變量不被序列化 意思就是:不能序列化一個transient變量。
(具體可以參考:
序列化)
使用場景有:
使用淺拷貝:對象隻有原始資料類型字段;有引用類型的字段,但從不更改它。
使用深拷貝:有引用類型的字段,且經常被修改。
它非常簡單,如果對象隻有原始字段,那麼顯然你會去淺層複制,但是如果對象引用了其他對象,那麼根據請求,應該選擇淺拷貝或深拷貝。我的意思是,如果引用不随時修改,那麼進行深層複制就沒有意義了。你可以選擇淺拷貝。但如果參考檔案經常被修改,那麼您需要進行深度複制。再次沒有硬性規定,這一切都取決于要求。
問:什麼我們在派生類中覆寫Object的clone()方法時,一定要調用super.clone()呢?
答:在運作時刻,Object中的clone()識别出你要複制的是哪一個對象,然後為此對象配置設定空間,并進行對象的複制,将原始對象的内容一一複制到新對象的存儲空間中。
題外話:
如果是單例模式,你以這種方式進行深拷貝,則它就不再是單例模式了。
參考:
https://blog.csdn.net/zhangjg_blog/article/details/18369201 https://www.jianshu.com/p/8c74edbb46c0