• java对象的克隆以及深拷贝与浅拷贝


    一、为什么要使用克隆

      在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在 Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。

      使用new和赋值语句或者set注入都是可以的,但是,这会花费大量开销去做,效率低,并且还会产生冗余代码。Object的clone()方法是在java平台层实现的native方法,具有开销小,速度快的特点。

    二、克隆的实现

    1. 需要克隆的类实现Cloneable接口
    2. 需要克隆的类复写clone方法,并在方法中调用父类的clone方法
    package com.javaBase.clone;
    
    /**
     * 〈一句话功能简述〉;
     * 〈对象克隆〉
     *
     * @author jxx
     * @see [相关类/方法](可选)
     * @since [产品/模块版本] (可选)
     */
    public class Workers implements Cloneable {
    
        /**
         * 复写父类的clone方法
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        public static void main(String[] args) throws Exception {
            Workers w = new Workers();
            Workers w2 = (Workers)w.clone();
            System.out.println(w == w2);
        }
    }

    运行结果:false

    显然w2是w的克隆,并且是一个新的对象。这便是简单的克隆实现。

    三、深克隆与浅克隆

      浅拷贝:仅仅克隆基本类型变量,而不克隆引用类型的变量
      深克隆:既克隆基本类型变量,也克隆引用类型变量

    代码示例:

    package com.javaBase.clone;
    
    /**
     * 〈一句话功能简述〉;
     * 〈功能详细描述〉
     *
     * @author jxx
     * @see [相关类/方法](可选)
     * @since [产品/模块版本] (可选)
     */
    public class Boss implements Cloneable {
    
        private String name;
        private char sex;
    
        public Boss(String name, char sex) {
            this.name = name;
            this.sex = sex;
        }
    
        public char getSex() {
            return sex;
        }
    
        public void setSex(char sex) {
            this.sex = sex;
        }
    
        public String getName() {
    
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    package com.javaBase.clone;
    
    /**
     * 〈一句话功能简述〉;
     * 〈浅拷贝〉
     *
     * @author jxx
     * @see [相关类/方法](可选)
     * @since [产品/模块版本] (可选)
     */
    public class Workers implements Cloneable {
    
        private String name;
        private char sex;
        private Boss boss;
    
        public Workers(String name, char sex, Boss boss) {
            this.name = name;
            this.sex = sex;
            this.boss = boss;
        }
    
        /**
         * 复写父类的clone方法
         *
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        public static void main(String[] args) throws Exception {
            Boss boss = new Boss("马云",'男');
            Workers w = new Workers("张三",'女',boss);
            Workers w2 = (Workers) w.clone();
            System.out.println(w.name == w2.name);      //true
            System.out.println(w.sex == w2.sex);        //true
            System.out.println(w.boss == w2.boss);      //true
    
            w.name = "李四";
            System.out.println(w.name == w2.name);      //false
    
            w.boss.setName("马化腾");
            System.out.println(w.boss.getName() == w2.boss.getName());  //true 修改w对象的boss的属性值,w2对象中的值也跟随发生了变化
        }
    }

    以上是一个浅拷贝的例子,Boss是一个引用类型,当w对象克隆体w2中的boss引用和w2指向的是同一片内存区域,因此,修改其中一个的boss属性值。另一个也会随着发生变化。因此当对象属性中存在引用类型时,需要进行深拷贝。下面是深拷贝代码:

    package com.javaBase.clone;
    
    /**
     * 〈一句话功能简述〉;
     * 〈深拷贝〉
     *
     * @author jxx
     * @see [相关类/方法](可选)
     * @since [产品/模块版本] (可选)
     */
    public class Workers implements Cloneable {
    
        private String name;
        private char sex;
        private Boss boss;
    
        public Workers(String name, char sex, Boss boss) {
            this.name = name;
            this.sex = sex;
            this.boss = boss;
        }
    
        /**
         * 复写父类的clone方法
         *
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Workers w = (Workers)super.clone();
            //拷贝一份新的引用类型
            w.boss = (Boss)this.boss.clone();
            return w;
        }
    
        public static void main(String[] args) throws Exception {
            Boss boss = new Boss("马云",'男');
            Workers w = new Workers("张三",'女',boss);
            Workers w2 = (Workers) w.clone();
            System.out.println(w.name == w2.name);      //true
            System.out.println(w.sex == w2.sex);        //true
            System.out.println(w.boss == w2.boss);      //false
    
            w.name = "李四";
            System.out.println(w.name == w2.name);      //false
    
            w.boss.setName("马化腾");
            System.out.println(w.boss.getName() == w2.boss.getName());  //false 修改w对象的boss的属性值,w2对象中的值没有跟随变化
        }
    }

    可见深拷贝情况下,w2与w拥有两个不同的对象boss。

    四、String类型的特殊性

      我们知道对于引用类型需要进行深度拷贝,但String类型是个例外,String类型的变量clone后的表现好象也实现了深度clone,但其实只是一个假象。因为执行 cloned.name = "new";语句时,它作用相当于生成了一个新的string类型,然后又赋回给cloned.name。这是因为string被sun公司的工程师写成了一个不可更改的类(immutable class),在所有string类中的函数都不能更改自身的值。

    五、总结

    1. 浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用。直接使用clone方法,再嵌套的还是浅克隆,因为有些引用类型不能直接克隆。
    2. 深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,并且在clone方法中又对没有clone方法的引用类型又做差异化复制,克隆后的对象与原对象之间完全不会影响,但是内容完全相同。
    3. 使用序列化也能完成深复制的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。

      

    参考链接:什么是对象克隆(拷贝/复制)?浅克隆与深克隆有什么区别?

                      java对象克隆以及深拷贝和浅拷贝

  • 相关阅读:
    多线程、线程同步、Lock接口
    线程池、匿名内部类、创建线程
    commons-IO、多线程
    PHP的一个坑
    jquery 实现导航栏滑动效果
    浏览器加载模式:window.onload和$(document).ready()的区别(详解)
    jQuery绑定事件-多种方式实现
    baidu-fex 精彩文章
    jquery1.11 操作checkbox:全选、取消全选、获取选择元素、获取取消选择元素(总结)
    倒计时时钟-原创
  • 原文地址:https://www.cnblogs.com/jxxblogs/p/12053281.html
Copyright © 2020-2023  润新知