Java clone

问:为什么需要克隆?

答:因为在编程中会遇到一种情况,有一个 DemoBean 的对象实例 demo1 在某一时刻已经包含了一些有效值,此时可能会需要一个和 demo1 完全相同的新对象 demo2 且此后对 demo1 的任何改动都不影响到 demo1 中的值,在 Java 中用简单的赋值语句是不能满足这种需求的,所以我们需要使用其他的途径来保证 demo1 与 demo2 是两个独立的对象且 demo2 的初始值是由 demo1 对象确定的,而克隆就是其官方提供的一种接口定义(至少比主动 new 对象然后取值赋值方便)。

问:浅度克隆(浅拷贝)和深度克隆(深拷贝)的区别是什么?
  • 浅度克隆:
    被复制对象(一个新的对象实例)的所有变量都含有与原来的对象相同的值,对于基本数据类型的属性复制一份给新产生的对象,对于非基本数据类型的属性仅仅复制一份引用给新产生的对象(新实例中引用类型属性还是指向原来对象引用类型属性)。

  • 深度克隆:
    被复制对象(一个新的对象实例)的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象(新内存空间),而不再是原有的那些被引用的对象,换言之深度克隆把要复制的对象所引用的对象都复制了一遍,也就是在浅度克隆的基础上,对于要克隆的对象中的非基本数据类型的属性对应的类也实现克隆,这样对于非基本数据类型的属性复制的不是一份引用。

问:String 克隆的特殊性在哪里?StringBuffer 和 StringBuilder 呢?

答:由于基本数据类型都能自动实现深度 clone,引用类型默认实现的是浅度 clone,而 String 是引用类型的一个特例,我们可以和操作基本数据类型一样认为其实现了深度 clone(实质是浅克隆,切记只是一个假象),由于 String 是不可变类,对于 String 类中的很多修改操作都是通过新 new 对象复制处理的,所以当我们修改 clone 前后对象里面 String 属性的值时其实都是属性引用的重新指向操作,自然对 clone 前后对象里 String 属性是没有相互影响的,类似于深度克隆;所以虽然他是引用类型而且我们在深度克隆时无法调用其 clone 方法,但是其不影响我们深度克隆的使用。

如果要实现深度克隆则 StringBuffer 和 StringBuilder 是需要主动特殊处理的,否则就是真正的对象浅克隆,所以处理的办法就是在类的 clone 方法中对 StringBuffer 或者 StringBuilder 属性进行如下主动拷贝操作。
再次强调,这个区别非常重要,即便不是面试题自己开发中也要注意这个坑。

问:Java 中集合默认的 clone 是深度克隆还是浅度克隆,有什么开发经验可以分享?

答:集合的默认 clone() 方法都是浅克隆,而且集合类提供的拷贝构造方法或 addAll、add 等方法都是浅克隆,也就是说存储在原集合和克隆集合中的对象会保持一致并指向堆中同一内存地址。关于集合克隆(拷贝)的开发经验其实就是一些常见场景的归类分析,具体如下。

常见的集合浅克隆(拷贝复制)操作经验如下:

    //使用集合默认的 clone 方法复制(浅)
    List<InfoBean> destList = (List<InfoBean>) srcList.clone(); //使用 add 方法循环遍历复制(浅) 
    List<InfoBean> destList = new ArrayList<InfoBean>(srcList.size()); for(
    InfoBean bean :srcList )

    {
        destList.add(bean);
    } //使用 addAll 方法复制(浅)

    List<InfoBean> destList = new ArrayList<InfoBean>(); destList.addAll(srcList );
    //使用构造方法复制(浅) 
    List<InfoBean> destList = new ArrayList<InfoBean>(srcList);
    //使用System.arraycopy()方法复制(浅) 
    InfoBean[] srcBeans = srcList.toArray(new InfoBean[0]);
    InfoBean[] destBeans = new InfoBean[srcBeans.length]; System.arraycopy(srcBeans ,0,destBeans ,0,srcBeans.length );

常见的集合浅克隆(拷贝复制)操作经验如下:


    //通过add和clone实现集合深度拷贝 
    class InfoBean implements Cloneable {
        public String name;
        public int age;

        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    } for(
    int index = 0; index<src.size();index ++)

    {
        destList.add((InfoBean) srcList.get(index).clone());
    } //使用序列化方法对集合进行深度拷贝

    public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
        objOut.writeObject(src);
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream objIn = new ObjectInputStream(byteIn);
        return (List<T>) objIn.readObject();
    }

    List<InfoBean> destList = deepCopy(srcList);
问:下面程序的运行结果是什么?为什么?
        class InfoBean {
            public String name;
            public int age;
        }

        InfoBean bean1 = new InfoBean();
        InfoBean bean2 = new InfoBean();
        List<InfoBean> srcList = new ArrayList();
        srcList.add(bean1);
        srcList.add(bean2);
        ArrayList<InfoBean> destList = (ArrayList<InfoBean>) srcList.clone();
        destList.remove(0);
        System.out.println("srcList=" + srcList.size());
        System.out.println("destList=" + destList.size());

答:本题考查 Java 集合的浅 clone 特性,答案如下。

srcList=2
destList=1

因为 destList 是 srcList 的浅 clone,所以当使用 remove 方法移除掉集合中的对象且不修改集合中对象的值时只是在 destList 的 List 内部实现数组中移除了指向元素的地址,故对 srcList 无影响。

问:实现对象克隆常见的方式有哪些,具体怎么做?

答:常见的实现方式主要有三种。

  • 通过自己写一个克隆方法里面 new 一个同样的对象来进行 get、set 依次赋值实现深度克隆(很繁琐且易出错);

  • 通过实现 Cloneable 接口并重写 Object 类的 clone() 方法(分为深浅两种方式);

  • 通过实现 Serializable 接口并用对象的序列化和反序列化来实现真正的深度克隆;

自己实现方法 new 对象 get、set 的方式因为非常不优雅和没有实际作用,所以不再给出具体示例。

通过实现 Cloneable 接口并重写 Object 类的 clone() 方法实现浅克隆做法:
Object 类中 clone 方法的默认实现最终是一个 native 方法(如果 clone 类没有实现 Cloneable 接口并调用了 Object 的 clone 方法就会抛出 CloneNotSupportedException 异常,因为 clone 方法默认实现中有使用 instanceof 进行接口判断),相对来说效率高些,默认实现是先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容,对基本数据类型就是值复制,而对非基本类型变量保存的仅仅是对象的引用,所以会导致 clone 后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

        class InfoBean implements Cloneable {
            public String name;
            public int age;

            public InfoBean clone() {
                try {
                    return (InfoBean) super.clone();
                } catch (CloneNotSupportedException e) {
                    return null;
                }
            }
        }

通过实现 Cloneable 接口并重写 Object 类的 clone() 方法实现深克隆做法:
在浅度克隆的基础上对于要克隆对象中的非基本数据类型的属性对应的类也实现克隆,这样对于非基本数据类型的属性复制的不是一份引用。

        class InfoBean implements Cloneable {
            public String name;
            public int age;

            public Object clone() {
                try {
                    return super.clone();
                } catch (CloneNotSupportedException e) {
                    return null;
                }
            }
        }
        class PeopleBean implements Cloneable {
            public String vipId;
            public InfoBean infoBean;

            public Object clone() {
                try {
                    PeopleBean bean = (PeopleBean) super.clone();
                    bean.infoBean = (InfoBean) infoBean.clone();
                    return bean;
                } catch (CloneNotSupportedException e) {
                    return null;
                }
            }
        }

通过 Serializable 接口并用对象的序列化和反序列化来实现真正的深度克隆做法:
对象序列化操作可以将对象的状态转换成字节流传输或者存储再生,我们可以借用这一特点实现对象的深度克隆,特别是当我们的对象嵌套非常复杂且想实现深度克隆时如果使用序列化方式会大大减少代码量。

        class CloneUtil {
            public static <T extends Serializable> T clone(T obj) {
                T cloneObj = null;
                try {
                    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                    ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
                    objOut.writeObject(obj);
                    objOut.close();
                    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
                    ObjectInputStream objIn = new ObjectInputStream(byteIn);
                    cloneObj = (T) objIn.readObject();
                    objIn.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                return cloneObj;
            }
        }
        class InfoBean implements Serializable {
            public String name;
            public int age;
        }
        class PeopleBean implements Serializable {
            public String vipId;
            public InfoBean infoBean;

            public Object clone() {
                return CloneUtil.clone(this);
            }
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • 在java中,如果需要有拷贝问题,都会使用到父类Object的Clone方法,能够为我们提供对象的拷贝方法,在使用...
    三木仔阅读 1,508评论 0 2
  • 1 场景问题# 1.1 订单处理系统## 考虑这样一个实际应用:订单处理系统。 现在有一个订单处理的系统,里面有个...
    七寸知架构阅读 4,420评论 3 63
  • Java Clone 平时项目中用的也不多,今天来实践下Java的Clone。Clone主要分为“浅拷贝”与“深拷...
    昵称全尼马被注册了阅读 746评论 0 3
  • 更多 Java 高级知识方面的文章,请参见文集《Java 高级知识》 首先看下面这一段代码: 如果是在 C++ 中...
    专职跑龙套阅读 473评论 0 0
  • 室小财容膝,墙低仅及肩。方过授衣月,又遇始裘天。寸积篝炉炭,铢称布被绵。平生师陋巷,随处一欣然。
    不拘一格99阅读 883评论 0 0