Java深克隆和浅克隆的原理及实现

Java深克隆和浅克隆的原理及实现

参考:
https://www.jianshu.com/p/94dbef2de298
https://www.cnblogs.com/shakinghead/p/7651502.html

Java 中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。

根据对对象属性的拷贝程度(基本数据类和引用类型),会分为两种:
浅拷贝 (Shallow Copy)
深拷贝 (Deep Copy)

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
结构图如下:


1.png
浅拷贝的实现

实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法

示例:

package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Teacher implements Cloneable {

    private String name;

    private int age;

    public Teacher(String name,int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Student implements Cloneable{

    private String name;

    private int age;

    private Teacher teacher;

    public Student(String name,int age,Teacher teacher){
        this.name = name;
        this.age = age;
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", teacher=" + teacher +
                '}';
    }

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

    public static void main(String[] args) throws CloneNotSupportedException {

        Teacher teacher = new Teacher("王老师",30);

        Student student = new Student("张三", 20, teacher);

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

        System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));

        Student student1 = (Student) student.clone();
        student1.setName("李四");
        student1.setAge(40);
        Teacher teacher1 = student1.getTeacher();
        teacher1.setAge(90);

        System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
        System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

    }
}

运行结果:

student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=30}},hashcode1872034366
teacher:Teacher{name='王老师', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老师', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老师', age=90},hashcode:1581781576
student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=90}},hashcode1872034366

由输出的结果可见,通过 student.clone() 拷贝对象后得到的 student1,和 student 是两个不同的对象。student 和 student1 的基础数据类型的修改互不影响,而引用类型 Teacher 修改后是会有影响的。
student的基础数据类型:name = "张三" ,age = 20
student1的基础数据类型:name = "李四" ,age = 90

student1的引用类型teacher,修改age = 90 之后,student 中teacher.age =90 ;
可以通过hascode打印的值看的出来,引用类型的是相同内存地址,所以修改copy后中teacher.age,直接影响原来student中teacher对象的age

深拷贝

通过上面的例子可以看到,浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 student 的 teacher,但是 student1 的 teacher 也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。

深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

  1. 深拷贝特点
    (1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
    (2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
    (3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
    (4) 深拷贝相比于浅拷贝速度较慢并且花销较大。

3.深拷贝的实现方法主要有两种:
(1)、通过重写clone方法来实现深拷贝
(2)、通过对象序列化实现深拷贝

结构图如下:


2.png

一、通过重写clone方法来实现深拷贝
示例:

package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Teacher implements Cloneable {

    private String name;

    private int age;

    public Teacher(String name,int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
package com.lvyuanj.core.model;

import lombok.Data;

@Data
public class Student implements Cloneable{

    private String name;

    private int age;

    private Teacher teacher;

    public Student(String name,int age,Teacher teacher){
        this.name = name;
        this.age = age;
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", teacher=" + teacher +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.teacher = (Teacher) teacher.clone();
        return student;
    }

    public static void main(String[] args) throws CloneNotSupportedException {

        Teacher teacher = new Teacher("王老师",30);

        Student student = new Student("张三", 20, teacher);

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

        System.out.println("teacher:"+teacher+ ",hashcode"+System.identityHashCode(teacher));

        Student student1 = (Student) student.clone();
        student1.setName("李四");
        student1.setAge(40);
        Teacher teacher1 = student1.getTeacher();
        teacher1.setAge(90);

        System.out.println("student1:"+student1 + ",hashcode:"+System.identityHashCode(student1));
        System.out.println("teacher1 "+teacher1 + ",hashcode:"+System.identityHashCode(student1.getTeacher()));

        System.out.println("student:"+student + ",hashcode"+System.identityHashCode(student));

    }
}

运行结果:

student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=30}},hashcode1872034366
teacher:Teacher{name='王老师', age=30},hashcode1581781576
student1:Student{name='李四', age=40, teacher=Teacher{name='王老师', age=90}},hashcode:1725154839
teacher1 Teacher{name='王老师', age=90},hashcode:1670675563
student:Student{name='张三', age=20, teacher=Teacher{name='王老师', age=30}},hashcode1872034366

二、通过对象序列化实现深拷贝
虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

示例:

package com.lvyuanj.core.model;

import lombok.Data;

import java.io.Serializable;

@Data
public class TeacherA implements Serializable {

    private String name;
    private int sex;

    public TeacherA(String name,int sex){
        this.name = name;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "TeacherA{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                '}';
    }
}

package com.lvyuanj.core.model;

import lombok.Data;

import java.io.*;

@Data
public class StudentA implements Serializable {

    private String name;
    private int sex;
    private TeacherA teacherA;

    public StudentA(String name,int sex,TeacherA teacherA){
        this.name = name;
        this.sex = sex;
        this.teacherA = teacherA;
    }

    @Override
    public String toString() {
        return "StudentA{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", teacherA=" + teacherA +
                '}';
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        TeacherA teacherA = new TeacherA("Arvin", 0);

        StudentA studentA = new StudentA("Tom", 1, teacherA);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(studentA);
        oos.flush();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        StudentA copystu = (StudentA) ois.readObject();

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));

        copystu.setName("copy-Arvin");
        copystu.getTeacherA().setName("copy-tom");

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
    }
}

运行结果:

studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:317574433
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1304836502
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1300109446
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:317574433
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:317574433

可以通过很简洁的代码即可完美实现深拷贝。不过要注意的是,如果某个属性被transient修饰,那么该属性就无法被拷贝了。
示例:

package com.lvyuanj.core.model;

import lombok.Data;

import java.io.*;

@Data
public class StudentA implements Serializable {

    private transient String name;
    private int sex;
    private TeacherA teacherA;

    public StudentA(String name,int sex,TeacherA teacherA){
        this.name = name;
        this.sex = sex;
        this.teacherA = teacherA;
    }

    @Override
    public String toString() {
        return "StudentA{" +
                "name='" + name + '\'' +
                ", sex=" + sex +
                ", teacherA=" + teacherA +
                '}';
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        TeacherA teacherA = new TeacherA("Arvin", 0);

        StudentA studentA = new StudentA("Tom", 1, teacherA);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(studentA);
        oos.flush();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        StudentA copystu = (StudentA) ois.readObject();

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));

        copystu.setName("copy-Arvin");
        copystu.getTeacherA().setName("copy-tom");

        System.out.println("studentA:"+studentA+",hashCode:"+System.identityHashCode(studentA));
        System.out.println("TeacherA:"+teacherA+",hashCode:"+System.identityHashCode(teacherA));

        System.out.println("copystu:"+copystu+",hashCode:"+System.identityHashCode(copystu));
        System.out.println("copystu teacher:"+copystu.getTeacherA()+",hashCode:"+System.identityHashCode(copystu));
    }
}

运行结果:

studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='Arvin', sex=0},hashCode:1854731462
studentA:StudentA{name='Tom', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:895328852
TeacherA:TeacherA{name='Arvin', sex=0},hashCode:1304836502
copystu:StudentA{name='copy-Arvin', sex=1, teacherA=TeacherA{name='copy-tom', sex=0}},hashCode:1854731462
copystu teacher:TeacherA{name='copy-tom', sex=0},hashCode:1854731462

以上代码中StudentA中name属性被transient修饰,copy之后name=null, 打印copystu:StudentA{name='null', sex=1, teacherA=TeacherA{name='Arvin', sex=0}},hashCode:1854731462

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

推荐阅读更多精彩内容