美文网首页Java拾遗
Java拾遗:008 - 对象克隆与浅拷贝、深拷贝

Java拾遗:008 - 对象克隆与浅拷贝、深拷贝

作者: ed72fd6aaa3c | 来源:发表于2018-08-04 19:42 被阅读23次

    对象克隆

    Object类中有一个方法叫clone,完整代码

        protected native Object clone() throws CloneNotSupportedException;
    

    首先它是一个Native方法,而且是受保护的(protected),抛出一个CloneNotSupportedException异常(JDK1.8)。

    通常程序员自己定义的类不能直接调用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方法,因为是一个本地方法,所以性能较好,如果没有特殊需求,不需要改写。该方法在重写时可以修改方法访问权限和返回值类型。

    Java中还有一个接口java.lang.Cloneable,该接口实际只是一个标记接口(不包含任何方法),与java.io.Serializable接口类似,只是告知外部程序,当前类可以调用clone方法。

    浅拷贝

    测试一下上述代码中的克隆方法

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

    通过测试可以看出,对象被成功克隆了,克隆后的对象与原对象不是同一个对象(使用==判断),基本类型的属性也被克隆了,修改原对象的属性值,克隆后的属性值并不受影响,但引用类型的属性却没有被克隆(实际克隆了引用,但引用的对象没有克隆),这种克隆方式就叫浅拷贝。
    浅拷贝就是只克隆对象本身,对象引用的其它对象则不拷贝。浅拷贝性能较好,对象内部的引用类型如不会修改,则使用浅拷贝足以。

    深拷贝

    如果克隆对象时,连同其引用类型属性引用的对象也克隆,则叫深拷贝。
    下面是针对上述代码实现的深拷贝

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

    对其进行相同测试

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

    可以看到引用对象也被拷贝了,克隆后的对象的引用类型属性与原对象同名引用类型属性引用的对象不再是同一个对象了。

    深拷贝的一个特殊情形是如果克隆对象的引用类型属性引用的对象内部又包含引用类型,那么就形成了多层拷贝,这种情况下进行深拷贝代码实现会复杂很多,所以有时为了简化实现过程会使得序列化机制,先序列化,再反序列化,得到的新对象与原对象会完全无关,从而实现深拷贝,但如无必要一般不建议使用,因为序列化通常性能比较差。

    也可以使用反射实现一个通用的克隆方法,实际就是《Java拾遗:006 - Java反射与内省》一文中的属性拷贝方法。

    源码

    相关文章

      网友评论

        本文标题:Java拾遗:008 - 对象克隆与浅拷贝、深拷贝

        本文链接:https://www.haomeiwen.com/subject/ivgmvftx.html