深克隆和浅克隆

浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。

简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象,如下图所示:

浅克隆

深克隆(Deep Clone)是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象,如下图所示:


深克隆
在 Java 语言中要实现克隆则需要实现 Cloneable 接口,并重写 Object 类中的 clone() 方法。
package com.hkj.springboot.cloneTest;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * @author liujy
 * @description Java中的克隆
 * @since 2020-12-11 11:23
 */
public class ArraysCopyOfMethodTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneObj cloneObj = new CloneObj();
        cloneObj.setName("cc");
        CloneObj backup = (CloneObj) cloneObj.clone();
        System.out.println(backup.getName());
    }

    @Getter
    @Setter
    @NoArgsConstructor
    static class CloneObj implements Cloneable {
        private String name;

        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

        public CloneObj(String name) {
            this.name = name;
        }
    }
}
克隆对象已经具有了原型对象的属性值

如果是数组类型,我们可以直接使用 Arrays.copyOf() 来实现克隆,那么 Arrays.copyOf() 是深克隆还是浅克隆呢?

验证代码如下:

package com.hkj.springboot.cloneTest;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Arrays;

/**
 * @author liujy
 * @description 验证Arrays.valueOf是深克隆还是浅克隆
 * @since 2020-12-11 11:23
 */
public class ArraysCopyOfMethodTest {
    public static void main(String[] args) {
        // 测试Arrays.valueOf是深克隆还是浅克隆
        Object[] obj1 = {new CloneObj("zhangsan"), "str"};
        Object[] obj2 = Arrays.copyOf(obj1, obj1.length);
        CloneObj c = (CloneObj) obj1[0];
        c.setName("lisi");
        obj1[1] = "string";
        CloneObj cc1 = (CloneObj) obj1[0];
        CloneObj cc2 = (CloneObj) obj2[0];
        System.out.println(cc1.getName() + "--" + obj1[1]);
        System.out.println(cc2.getName() + "--" + obj2[1]);
    }

    @Getter
    @Setter
    @NoArgsConstructor
    static class CloneObj implements Cloneable {
        private String name;

        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

        public CloneObj(String name) {
            this.name = name;
        }
    }
}

字符串值未变,对象元素属性值改变

由运行结果可知,Arrays.valueOf是浅克隆。因为数组比较特殊数组本身就是引用类型,因此在使用 Arrays.copyOf() 其实只是把引用地址复制了一份给克隆对象,如果修改了它的引用对象,那么指向它的(引用地址)所有对象都会发生改变,因此看到的结果是,修改了克隆对象的第一个元素,原型对象也跟着被修改了。

深克隆实现方式汇总

深克隆的实现方式有很多种,大体可以分为以下几类:

1.所有对象都实现克隆方法;
2.通过构造方法实现深克隆;
3.使用 JDK 自带的字节流实现深克隆;
4.使用第三方工具实现深克隆,比如 Apache Commons Lang;
5.使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。

所有对象都实现克隆方法
package com.hkj.springboot.cloneTest;

import lombok.Getter;
import lombok.Setter;

/**
 * @author liujy
 * @description 深克隆
 * @since 2020-12-11 14:22
 */
public class DeepCloneTest1 {
    @Getter
    @Setter
    static class CloneObj implements Cloneable {
        private String name;
        private CloneObjProp cloneObjProp;

        protected CloneObj clone() throws CloneNotSupportedException {
            CloneObj cloneObj = (CloneObj) super.clone();
            cloneObj.setCloneObjProp(this.cloneObjProp.clone());
            return cloneObj;
        }

        public CloneObj(String name) {
            this.name = name;
        }
    }

    @Getter
    @Setter
    static class CloneObjProp implements Cloneable {
        private String desc;

        protected CloneObjProp clone() throws CloneNotSupportedException {
            CloneObjProp cloneObjProp = (CloneObjProp) super.clone();
            return cloneObjProp;
        }

        public CloneObjProp(String desc) {
            this.desc = desc;
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        // 深拷贝实现-所有对象都实现Cloneable接口
        CloneObj cloneObj = new CloneObj("cc");
        CloneObjProp cloneObjProp = new CloneObjProp("this is cc");
        cloneObj.setCloneObjProp(cloneObjProp);
        // 进行克隆
        CloneObj cloneObjBackup = cloneObj.clone();
        // 修改原型对象
        cloneObj.setName("dd");
        cloneObj.getCloneObjProp().setDesc("this is dd");
        System.out.println(cloneObj.getName() + "--" + cloneObj.getCloneObjProp().getDesc());
        System.out.println(cloneObjBackup.getName() + "--" + cloneObjBackup.getCloneObjProp().getDesc());
    }
}

通过构造方法实现深克隆
package com.hkj.springboot.cloneTest;

import lombok.Getter;
import lombok.Setter;

/**
 * @author liujy
 * @description 深克隆
 * @since 2020-12-11 14:37
 */
public class DeepCloneTest2 {
    @Getter
    @Setter
    static class CloneObj {
        private String name;
        private CloneObjProp cloneObjProp;

        public CloneObj(String name) {
            this.name = name;
        }

        public CloneObj(String name, CloneObjProp cloneObjProp) {
            this.name = name;
            this.cloneObjProp = cloneObjProp;
        }
    }

    @Getter
    @Setter
    static class CloneObjProp {
        private String desc;

        public CloneObjProp(String desc) {
            this.desc = desc;
        }
    }

    public static void main(String[] args) {
        // 深拷贝实现-通过构造方法
        CloneObj cloneObj = new CloneObj("cc");
        CloneObjProp cloneObjProp = new CloneObjProp("this is cc");
        cloneObj.setCloneObjProp(cloneObjProp);
        // 通过构造方法进行克隆
        CloneObj cloneObjBackup = new CloneObj(cloneObj.getName(), new CloneObjProp(cloneObj.getCloneObjProp().getDesc()));
        // 修改原型对象
        cloneObj.setName("dd");
        cloneObj.getCloneObjProp().setDesc("this is dd");
        System.out.println(cloneObj.getName() + "--" + cloneObj.getCloneObjProp().getDesc());
        System.out.println(cloneObjBackup.getName() + "--" + cloneObjBackup.getCloneObjProp().getDesc());
    }
}

运行结果
克隆设计理念猜想

对于克隆为什么要这样设计,官方没有直接给出答案,我们只能凭借一些经验和源码文档来试着回答一下这个问题。Java 中实现克隆需要两个主要的步骤,一是 实现 Cloneable 空接口,二是重写 Object 的 clone() 方法再调用父类的克隆方法 (super.clone()),那为什么要这么做?

从源码中可以看出 Cloneable 接口诞生的比较早,JDK 1.0 就已经存在了,因此从那个时候就已经有克隆方法了,那我们怎么来标识一个类级别对象拥有克隆方法呢?克隆虽然重要,但我们不能给每个类都默认加上克隆,这显然是不合适的,那我们能使用的手段就只有这几个了:

在类上新增标识,此标识用于声明某个类拥有克隆的功能,像 final 关键字一样;

使用 Java 中的注解;

实现某个接口;

继承某个类。

先说第一个,为了一个重要但不常用的克隆功能, 单独新增一个类标识,这显然不合适;再说第二个,因为克隆功能出现的比较早,那时候还没有注解功能,因此也不能使用;第三点基本满足我们的需求,第四点和第一点比较类似,为了一个克隆功能需要牺牲一个基类,并且 Java 只能单继承,因此这个方案也不合适。采用排除法,无疑使用实现接口的方式是那时最合理的方案了,而且在 Java 语言中一个类可以实现多个接口。

那为什么要在 Object 中添加一个 clone() 方法呢?

因为 clone() 方法语义的特殊性,因此最好能有 JVM 的直接支持,既然要 JVM 直接支持,就要找一个 API 来把这个方法暴露出来才行,最直接的做法就是把它放入到一个所有类的基类 Object 中,这样所有类就可以很方便地调用到了。

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

推荐阅读更多精彩内容