Java 深拷贝与浅拷贝概念与代码实现

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 深拷贝与浅拷贝概念与代码实现相关的知识,希望对你有一定的参考价值。

文章目录

拷贝概念

关于拷贝,简单来说就是创建一个和已知对象一模一样的对象。Java中的拷贝包括深拷贝和浅拷贝,可能日常编码过程中用的不多,但是这是一个面试经常会问的问题。

什么叫Java浅拷贝?

浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化

什么叫Java深拷贝?

深拷贝复制变量值,对于引用数据,则递归至基本类型后,再复制。深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象

Java浅拷贝和深拷贝的区别是什么?

通俗来讲浅拷贝的复制其引用,当引用指向的值改变时也会跟着变化;而深拷贝则是与原来的对象完全隔离,互不影响。

拷贝的实现

浅拷贝的实现

首先,我们定义一下需要拷贝的简单对象。

我们定义了一个Student学生类,包含name姓名,和age年龄,sex性别,而是另一个School类,包含schoolName学校名称和stuNums学生数量以及Student学生,其中Student并不是字符串,而是一个Student类。

学校对象

package com.yyl.javaprogram.copy;

public class School implements Cloneable 
    private String schoolName;
    private int stuNums;
    private Student stu;

    public String getSchoolName() 
        return schoolName;
    

    public void setSchoolName(String schoolName) 
        this.schoolName = schoolName;
    

    public int getStuNums() 
        return stuNums;
    

    public void setStuNums(int stuNums) 
        this.stuNums = stuNums;
    

    public Student getStu() 
        return stu;
    

    public void setStu(Student stu) 
        this.stu = stu;
    

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

    @Override
    public String toString() 
        return "School" +
                "schoolName='" + schoolName + '\\'' +
                ", stuNums=" + stuNums +
                ", stu=" + stu +
                '';
    

学生对象:

package com.yyl.javaprogram.copy;

public class Student 
    private String name;
    private int age;
    private String sex;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

    public String getSex() 
        return sex;
    

    public void setSex(String sex) 
        this.sex = sex;
    

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


这时,我们要进行赋值的原始类 School。下面我们产生一个 School对象,并调用其 clone 方法复制一个新的对象。

注意:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法。

package com.yyl.javaprogram.copy;

public class LightCopy 
    public static void main(String[] args) throws CloneNotSupportedException 
        // 创建初始的School对象
        School s1 = new School();
        s1.setSchoolName("燕山大学");
        s1.setStuNums(20000);
        Student stu1 = new Student();
        stu1.setAge(18);
        stu1.setName("玉如梦");
        stu1.setSex("女");
        s1.setStu(stu1);
        // 调用重写的clone方法,clone出一个新的school---s2
        School s2 = (School) s1.clone();
        System.out.println("克隆s1出一个新的学校:s2");
        System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode());
        System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());
        //System.out.println(s1.getStu().getAge()==s2.getStu().getAge());
        System.out.println("----------------------------");
        System.out.println("修改克隆出来的对象");
        Student student2 = s2.getStu();
        student2.setAge(21);
        student2.setName("曲华裳");
        s2.setStu(student2);
        s2.setSchoolName("YSU");
        System.out.println("修改后两个学校学生一样,但是学校不是克隆的所以s2修改不影响s1");
        System.out.println("s1: "+s1+" s1的hashcode:"+s1.hashCode()+" s1中stu1的hashcode:"+s1.getStu().hashCode());
        System.out.println("s2: "+s2+" s2的hashcode:"+s2.hashCode()+" s2中stu1的hashcode:"+s2.getStu().hashCode());
        System.out.println(s1.getStu().getAge()==s2.getStu().getAge());
    


我们查看输出的结果

结果分析:这里对School类选择了两个具有代表性的属性值:一个是引用传递类型(Student对象);另一个是字符串类型(学校名:属于常量)和基本数据类型(年龄)。

通过拷贝构造方法进行了浅拷贝,各属性值成功复制。其中,p1值传递部分的属性值发生变化时,p2不会随之改变;而引用传递部分属性值发生变化时,p2也随之改变。

基本数据类型是值传递,所以修改值后不会影响另一个对象的该属性值;引用数据类型是地址传递(引用传递),所以修改值后另一个对象的该属性值会同步被修改。

String类型非常特殊。首先,String类型属于引用数据类型,不属于基本数据类型,但是String类型的数据是存放在常量池中的,也就是无法修改的!也就是说,当将name属性从“燕山大学”改为“YSU"后,并不是修改了这个数据的值,而是把这个数据的引用从指向”燕山大学“这个常量改为了指向”YSU“这个常量。在这种情况下,另一个对象的name属性值仍然指向”燕山大学“不会受到影响。

要注意:如果在拷贝构造方法中,对引用数据类型变量逐一开辟新的内存空间,创建新的对象,也可以实现深拷贝。而对于一般的拷贝构造,则一定是浅拷贝。

深拷贝的实现

深拷贝的方式有很多种,此次我们介绍三种方式

  • 方法一 构造函数
  • 方法二 重载clone()方法
  • 方法三Serializable序列化

方法一:构造函数

public static void constructorCopy() 
    Student student = new Student("小李", 21, "男");
    School school = new School("xx大学", 100, student);
    // 调用构造函数时进行深拷贝
    School copySchool = new School(school.getSchoolName(),
            school.getStuNums(),
            new Student(student.getName(),
            student.getAge(),
            student.getSex()));
    // 修改源对象的值
    copySchool.getStudent().setSex("女");
    // 检查两个对象的值不同
    System.out.println(school.hashCode() == copySchool.hashCode());

结果输出二者为false

方法二:重载clone()方法

Object父类有个clone()的拷贝方法,不过它是protected类型的,我们需要重写它并修改为public类型。除此之外,子类还需要实现Cloneable接口来告诉JVM这个类是可以拷贝的。

让我们还是看之前的School代码,修改clone函数

package com.yyl.javaprogram.copy;

public class School implements Cloneable 
    private String schoolName;
    private int stuNums;
    private Student stu;

    public String getSchoolName() 
        return schoolName;
    

    public void setSchoolName(String schoolName) 
        this.schoolName = schoolName;
    

    public int getStuNums() 
        return stuNums;
    

    public void setStuNums(int stuNums) 
        this.stuNums = stuNums;
    

    public Student getStu() 
        return stu;
    

    public void setStu(Student stu) 
        this.stu = stu;
    

    @Override
    protected School clone() throws CloneNotSupportedException 
        School school = (School) super.clone();
        school.stu = (Student) stu.clone();
        return school;
    

    @Override
    public String toString() 
        return "School [schoolName=" + schoolName + ", stuNums=" + stuNums + ", stu=" + stu + "]";
    

Student修改clone函数

package com.yyl.javaprogram.copy;

public class Student implements Cloneable 
    private String name;
    private int age;
    private String sex;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

    public String getSex() 
        return sex;
    

    public void setSex(String sex) 
        this.sex = sex;
    

    @Override
    public String toString() 
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
    

    @Override
    protected Student clone() throws CloneNotSupportedException 
        return (Student) super.clone();
    


这样再进行clone就是全部进行拷贝了

package com.yyl.javaprogram.copy;

public class DeepCopy 
    public static void main(String[] args) throws CloneNotSupportedException 
        Student student = new Student ();
        student.setName("得到");
        student.setAge(15);
        student.setSex("男");
        School school = new School ();
        school.setSchoolName("kkk");
        school.setStuNums(22);
        school.setStu(student);

       School school2 = school.clone();

        // 检查两个对象的值不同
        System.out.println(school.hashCode()==school2.hashCode());
    


方法三:Serializable序列化

我们看如下的代码

package com.yyl.javaprogram.copy;

import java.io.*;

public class People implements Serializable 

    private String name;
    private Student student;

    public People(String name, Student student) 
        this.name = name;
        this.student = student;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public Student getAddress() 
        return student;
    

    public void setAddress(Student address) 
        this.student = address;
    

    public Object deepClone() throws Exception 
        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        // 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    

注意 要使用序列化的方式来复制对象 对象需要继承Serializable接口,
接下来我们查看测试类

package com.yyl.javaprogram.copy;

public class DeepCopy 
    public static void main(String[] args) throws Exception 
        Student student = new Student ();
        student.setName("得到");
        student.setAge(15);
        student.setSex("男");
        People people = new People("ddd",student);


        People people1 = (People) people.deepClone();

        // 检查两个对象的值不同
        System.out.println(people1.hashCode()==people1.hashCode());
    


结果如下:

通过比较people对象和克隆的people1对象的hashCode发现,也是不同的对象

以上是关于Java 深拷贝与浅拷贝概念与代码实现的主要内容,如果未能解决你的问题,请参考以下文章

深拷贝与浅拷贝的区别,实现深拷贝的几种方法

JS深拷贝与浅拷贝的区别,实现深拷贝的几种方法

JavaScript深拷贝与浅拷贝

Java轻松理解深拷贝与浅拷贝

深拷贝与浅拷贝

深拷贝与浅拷贝