基礎面試題:深拷貝和淺拷貝詳解以及實例

深拷貝 vs 淺拷貝

淺拷貝

概念

複製基本類型的屬性;引用類型的屬性複製,複製棧中的變量 和 變量指向堆內存中的對象的指針,不復制堆內存中的對象。

如圖:

在這裏插入圖片描述

特點

​ 1.對於基本數據類型的成員對象,因爲基礎數據類型是值傳遞的,所以是直接將屬性值賦值給新的對象。基礎類型的拷貝,其中一個對象修改該值,不會影響另外一個。
2.對於引用類型,比如數組或者類對象,因爲引用類型是引用傳遞,所以淺拷貝只是把內存地址賦值給了成員變量,它們指向了同一內存空間。改變其中一個,會對另外一個也產生影響。

實現

​ 實現 Cloneable 接口,重寫 clone() 方法

/**
 * @ClassName ShallowCopyDemo
 * @Description: 實現 Cloneable 接口,重寫 clone() 方法。
 * @Author fking
 * @Date 2020/4/30 0030
 * @Version V1.0
 **/
public class ShallowCopyDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person(1, "fking01");//創建對象 Person p1
        Person p2 = (Person)p1.clone();//克隆對象 p1
        p2.setName("fking02");//修改 p2的name屬性,p1的name未變
        System.out.println(p1);
        System.out.println(p2);

        p2 = p1;
        p2.setName("fking02");//修改 p2的name屬性,p1的也跟着變
        System.out.println(p1);
        System.out.println(p2);
    }
}

class Person implements Cloneable {

    private int pid;

    private String name;

    public Person(int pid, String name) {
        this.pid = pid;
        this.name = name;
        System.out.println("Person constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person [pid:" + pid + ", name:" + name + "]";
    }
}

深拷貝

概念

​ 複製基本類型的屬性;引用類型的屬性複製,複製棧中的變量 和 變量指向堆內存中的對象的指針和堆內存中的對象。

在這裏插入圖片描述

特點

​ 1.對於基本數據類型的成員對象,因爲基礎數據類型是值傳遞的,所以是直接將屬性值賦值給新的對象。基礎類型的拷貝,其中一個對象修改該值,不會影響另外一個(和淺拷貝一樣)。

​ 2.對於引用類型,比如數組或者類對象,深拷貝會新建一個對象空間,然後拷貝里面的內容,所以它們指向了不同的內存空間。改變其中一個,不會對另外一個也產生影響。

​ 3.對於有多層對象的,每個對象都需要實現 Cloneable 並重寫 clone() 方法,進而實現了對象的串行層層拷貝。

​ 4.深拷貝相比於淺拷貝速度較慢並且花銷較大。

實現方式

​ 1.對象的屬性的Class 也實現 Cloneable 接口,在克隆對象時也手動克隆屬性

/**
 * @ClassName DeepCopyDemo01
 * @Description: 對象的屬性的Class 也實現 Cloneable 接口,在克隆對象時也手動克隆屬性。
 * @Author fking
 * @Date 2020/4/30 0030
 * @Version V1.0
 **/
public class DeepCopyDemo01 {
    public static void main(String[] args) throws CloneNotSupportedException {
        DPerson01 p1 = new DPerson01(1, "fking01", new DFood01("food01"));//創建Person 對象 p1
        DPerson01 p2 = (DPerson01)p1.clone();//克隆p1
        p2.setName("fking02");//修改p2的name屬性
        p2.getFood().setName("food02");//修改p2的自定義引用類型 food 屬性
        System.out.println(p1);//修改p2的自定義引用類型 food 屬性被改變,p1的自定義引用類型 food 屬性也隨之改變,說明p2的food屬性,只拷貝了引用,沒有拷貝food對象
        System.out.println(p2);
    }
}

class DPerson01 implements Cloneable {

    private int pid;

    private String name;

    private DFood01 food;

    public DPerson01(int pid, String name, DFood01 food) {
        this.pid = pid;
        this.name = name;
        this.food = food;
        System.out.println("DPerson01 constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 對象的屬性的Class 也實現 Cloneable 接口,在克隆對象時也手動克隆屬性。
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        DPerson01 p = (DPerson01)super.clone();
        p.setFood((DFood01)p.getFood().clone());
        return p;
    }

    @Override
    public String toString() {
        return "DPerson01 [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
    }

    public DFood01 getFood() {
        return food;
    }

    public void setFood(DFood01 food) {
        this.food = food;
    }

}

class DFood01 implements Cloneable {

    private String name;

    public DFood01(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

​ 2.結合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷貝

/**
 * @ClassName DeepCopyDemo02
 * @Description: 結合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷貝
 * @Author fking
 * @Date 2020/4/30 0030
 * @Version V1.0
 **/
public class DeepCopyDemo02 {
    public static void main(String[] args) throws CloneNotSupportedException {
        DPerson02 p1 = new DPerson02(1, "fking01", new SFood02("food01"));//創建 DPerson02 對象 p1
        DPerson02 p2 = (DPerson02)p1.cloneBySerializable();//克隆 p1
        p2.setName("fking02");//修改 p2 的 name 屬性
        p2.getFood().setName("food02");//修改 p2 的自定義引用類型 food 屬性
        System.out.println(p1);//修改 p2 的自定義引用類型 food 屬性被改變,p1的自定義引用類型 food 屬性未隨之改變,說明p2的food屬性,只拷貝了引用和 food 對象
        System.out.println(p2);
    }
}

class DPerson02 implements Cloneable, Serializable {

    private static final long serialVersionUID = -7710144514831611031L;

    private int pid;

    private String name;

    private SFood02 food;

    public DPerson02(int pid, String name, SFood02 food) {
        this.pid = pid;
        this.name = name;
        this.food = food;
        System.out.println("DPerson02 constructor call");
    }

    public int getPid() {
        return pid;
    }

    public void setPid(int pid) {
        this.pid = pid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 通過序列化完成克隆
     * @return
     */
    public Object cloneBySerializable() {
        Object obj = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            obj = ois.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return obj;
    }

    @Override
    public String toString() {
        return "DPerson02 [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
    }

    public SFood02 getFood() {
        return food;
    }

    public void setFood(SFood02 food) {
        this.food = food;
    }

}

class SFood02 implements Serializable {

    private static final long serialVersionUID = -3443815804346831432L;

    private String name;

    public SFood02(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

如果大家對java架構相關感興趣,可以關注下面公衆號,會持續更新java基礎面試題, netty, spring boot,spring cloud等系列文章,一系列乾貨隨時送達, 超神之路從此展開, BTAJ不再是夢想!

架構殿堂

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章