Java中淺拷貝和深拷貝詳解

Java淺拷貝深拷貝

淺拷貝和深拷貝涉及到瞭Object類中的clone()方法

在這裡插入圖片描述

實現淺拷貝

淺拷貝的實現需要類重寫clone()方法

淺拷貝會創建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝

如果屬性是基本類型,拷貝的就是基本類型的值;

如果屬性是內存地址(引用類型),拷貝的就是內存地址 ,因此如果其中一個對象改變瞭這個地址,就會影響到另一個對象,導致兩個對象的引用不等

實現淺拷貝很簡單隻需要將類實現Cloneable接口然後重寫clone方法即可

class Person implements Cloneable {
    String name;
    int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    /**
         * 重寫clone()方法
         *
         * @return
         * @throws CloneNotSupportedException
         */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}

測試淺拷貝特性

public void testClone() throws CloneNotSupportedException {
    Person person1 = new Person();
    person1.setName("ccy");
    person1.setAge(20);
    Person person2 = (Person) person1.clone();
    //查看淺拷貝效果
    System.out.println(person1);
    System.out.println(person2);
    System.out.println(person1.getName() == person2.getName());
    //驗證clone()的特性
    System.out.println(person1.clone() != person1);
    System.out.println(person1.clone().getClass() == person1.getClass());
    //如果是基本類型淺拷貝直接賦值值,如果是引用類型淺拷貝指向其內存地址即共享內存地址
    //改變person1的引用類型String屬性的值,引用發生改變
    person1.setName("zfs");
    System.out.println(person2.getName());
}

在這裡插入圖片描述

實現深拷貝

對於上述的問題雖然拷貝的兩個對象不同,但其內部的一些引用還是相同的,怎麼樣絕對的拷貝這個對象,使這個對象完全獨立於原對象呢?就使用我們的深拷貝瞭。深拷貝:在對引用數據類型進行拷貝的時候,創建瞭一個新的對象,並且復制其內的成員變量。

在具體實現深拷貝上,這裡提供兩個方式,重寫clone()方法和序列法。

重寫clone()方法

如果使用重寫clone()方法實現深拷貝,那麼要將類中所有自定義引用變量的類也去實現Cloneable接口實現clone()方法。對於字符類可以創建一個新的字符串實現拷貝。但是對於自定義類需要實現cloneable重寫clone,這樣做就太麻煩瞭所以我們使用序列化

序列化

序列化後將二進制字節流內容寫到一個媒介(文本或字節數組),然後是從這個媒介讀取數據,原對象寫入這個媒介後拷貝給clone對象,原對象的修改不會影響clone對象,因為clone對象是從這個媒介讀取。

熟悉對象緩存的知道我們經常將Java對象緩存到Redis中,然後還可能從Redis中讀取生成Java對象,這就用到序列化和反序列化。一般可以將Java對象存儲為字節流或者json串然後反序列化成Java對象。因為序列化會儲存對象的屬性但是不會也無法存儲對象在內存中地址相關信息。所以在反序列化成Java對象時候會重新創建所有的引用對象。

在具體實現上,自定義的類需要實現Serializable接口

class Person implements Serializable {
    String name;
    int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    @Override
    public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

    protected Person deepClone() throws Exception {
        //序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        //反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return (Person) ois.readObject();
    }
}

測試方法

public void testClone() throws Exception {
    Person person1 = new Person();
    person1.setName("ccy");
    person1.setAge(20);
    Person person2 = person1.deepClone();
    System.out.println(person1.getName() == person2.getName());
}

在這裡插入圖片描述

可以看到兩個引用對象的地址並不同,成功實現瞭深拷貝

到此這篇關於Java中淺拷貝和深拷貝詳解的文章就介紹到這瞭,更多相關Java淺拷貝深拷貝內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: