天天看點

java中的淺拷貝和深拷貝

          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

繼續閱讀