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

你可能感兴趣的:(Java深克隆和浅克隆的原理及实现)