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反射与内省》一文中的属性拷贝方法。

源码

相关文章
相关标签/搜索