Java拾遺:008 - 對象克隆與淺拷貝、深拷貝

對象克隆

Object類中有一個方法叫clone,完整代碼java

protected native Object clone() throws CloneNotSupportedException;

首先它是一個Native方法,並且是受保護的(protected),拋出一個CloneNotSupportedException異常(JDK1.8)。git

一般程序員本身定義的類不能直接調用clone方法,若是要在外部調用,須要重寫該方法程序員

static class FullName {
    private String firstName;
    private String lastName;

    public FullName(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

static class User implements Cloneable {
    private FullName name;
    private int age;

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

}

上述示例中直接調用了父類(Object)的clone方法,由於是一個本地方法,因此性能較好,若是沒有特殊需求,不須要改寫。該方法在重寫時能夠修改方法訪問權限和返回值類型。github

Java中還有一個接口java.lang.Cloneable,該接口實際只是一個標記接口(不包含任何方法),與java.io.Serializable接口相似,只是告知外部程序,當前類能夠調用clone方法。ide

淺拷貝

測試一下上述代碼中的克隆方法性能

@Test
    public void test() throws CloneNotSupportedException {

        // 構造一個對象
        User user = new User();
        user.age = 17;
        user.name = new FullName("Jack", "Jones");

        // 克隆對象
        User other = user.clone();

        // 檢查克隆結果
        // 克隆的對象非空
        assertNotNull(other);
        // 克隆的對象與原對象再也不是同一個對象
        assertFalse(user == other);
        // 克隆後基本類型被克隆,因此原對象修改該屬性值不影響非克隆後的對象屬性
        user.age = 18;
        assertEquals(17, other.age);
        // 克隆後引用類型僅克隆了引用,而引用對象沒有被克隆,因此二者指向同一個對象
        assertTrue(user.name == other.name);

    }

經過測試能夠看出,對象被成功克隆了,克隆後的對象與原對象不是同一個對象(使用==判斷),基本類型的屬性也被克隆了,修改原對象的屬性值,克隆後的屬性值並不受影響,但引用類型的屬性卻沒有被克隆(實際克隆了引用,但引用的對象沒有克隆),這種克隆方式就叫淺拷貝。 淺拷貝就是隻克隆對象自己,對象引用的其它對象則不拷貝。淺拷貝性能較好,對象內部的引用類型如不會修改,則使用淺拷貝足以。測試

深拷貝

若是克隆對象時,連同其引用類型屬性引用的對象也克隆,則叫深拷貝。 下面是針對上述代碼實現的深拷貝this

public User deepClone() throws CloneNotSupportedException {
        // 先用Object中的克隆方法(底層方法,性能相對更好)
        User other = (User) super.clone();
        // 將引用類型再克隆一次(注意若是引用類型中還有引用類型,那麼一樣須要克隆)
        // 多層克隆實現會比較麻煩,簡單的作法是先序列化再反序列化一次便可(缺點是序列化的性能一般較差)
        if (other.name != null) {
            // 因爲FullName類沒有實現克隆方法,因此這裏直接從新new一個
            other.name = new FullName(this.name.firstName, this.name.lastName);
        }
        return other;
    }

對其進行相同測試.net

@Test
    public void test() throws CloneNotSupportedException {

        // 構造一個對象
        User user = new User();
        user.age = 17;
        user.name = new FullName("Jack", "Jones");

        // 若是要實現深拷貝,須要將引用的對象也拷貝
        User other = user.deepClone();

        // 測試深拷貝效果
        assertFalse(user == other);
        assertEquals(user.age, other.age);
        assertTrue(user.name != other.name);
    }

能夠看到引用對象也被拷貝了,克隆後的對象的引用類型屬性與原對象同名引用類型屬性引用的對象再也不是同一個對象了。code

深拷貝的一個特殊情形是若是克隆對象的引用類型屬性引用的對象內部又包含引用類型,那麼就造成了多層拷貝,這種狀況下進行深拷貝代碼實現會複雜不少,因此有時爲了簡化實現過程會使得序列化機制,先序列化,再反序列化,獲得的新對象與原對象會徹底無關,從而實現深拷貝,但如無必要通常不建議使用,由於序列化一般性能比較差。

也可使用反射實現一個通用的克隆方法,實際就是《Java拾遺:006 - Java反射與內省》一文中的屬性拷貝方法。

源碼

相關文章
相關標籤/搜索