Java 中的浅拷贝和深拷贝

开发过程中,有时会遇到把现有的一个对象的所有成员属性拷贝给另一个对象的需求。这个时候就会用到拷贝这个概念。把原对象定义成 A,拷贝后的对象定义成 B,如果只是单纯使用 clone 方法进行拷贝,你会发现:

  1. 对于八个基本类型,会拷贝其值,并且 B 的改变不会影响 A。

  2. 如果是一个对象,拷贝的是地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。B 对该值的改变会影响 A。

  3. 对于 String字符串,这个比较特殊,虽然拷贝的也是引用,但是在修改的时候,它会从字符串池中重新生成新的字符串,原有的字符串对象保持不变。

这种只单纯拷贝引用地址的动作就是浅拷贝。

相反,如果拷贝一个对象时不是简单的将地址引用拷贝出来,而是新建了一个对象,这种方式就是深拷贝。

浅拷贝代码模拟

通过代码模拟浅拷贝的过程:

首先,新建两个实体类,学生和老师:

public class Teacher {
    private int id;
    private String name;
    public Teacher(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public void setId(int id) {
        this.id = id;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

接下来是学生,学生的实体类需要实现 clone

public class Student implements Cloneable {
    private int id;
    private String name;
    private Teacher teacher;
    public Student(int id, String name, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.teacher = teacher;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

接下来就来看一下浅拷贝的效果

public static void main(String[] args) throws CloneNotSupportedException {
    //新建一个student1
    Student student1=new Student(1,"javayz",new Teacher(1,"teacher1"));
    //student2从student1中克隆过去
    Student student2= (Student) student1.clone();
    //修改基本类型 int
    student2.setId(2);
    //修改String
    student2.setName("javayz2");
    //修改对象类型teacher
    Teacher teacher = student2.getTeacher();
    teacher.setName("teacher2");
    System.out.println(student1); //{id=1,name='javayz',teacher=Teacher{id=1,name='teacher2'}}
    System.out.println(student2); //{id=2,name='javayz2',teacher=Teacher{id=1,name='teacher2'}}
}

在这里插入图片描述
通过结果就可以发现,修改被克隆对象的基本类型和 String 类型不会对原来数据造成影响,但是由于用的是同一个引用地址,修改对象时两边都会被修改。

深拷贝代码模拟

深拷贝的其中一个方法是把被拷贝对象中的所有引用类型也都实现深拷贝,最后逐层拷贝实现引用地址是新的而不是用的同一个。

修改上面的 teacher 对象代码,实现 clone 方法

public class Teacher implements Cloneable{
    private int id;
    private String name;
    //省略构造方法、get、set、toString方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

修改 student 类的 clone 方法

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

然后执行同样的测试代码后就会发现两个对象已经互相不影响了。

第二个方法是利用 serializable 实现深拷贝,这种方式的原理在于通过 IO 流的方式先将序列化后的对象写进 IO 流中,再取出来实现深拷贝。这种方式下所有涉及到的类都必须实现 Serializable 接口

新建一个 DeepStudent 类,同时需要使得 Teacher 类也实现 Serializable 接口

public class DeepStudent implements Serializable {
    private static final long serialVersionUID=1L;
    private int id;
    private String name;
    private Teacher teacher;
    //省略构造方法、get、set、toString方法
    public Object deepCopy(){
        try {
            //将对象写到IO流中
            ByteArrayOutputStream bos=new ByteArrayOutputStream();
            ObjectOutputStream oos=new ObjectOutputStream(bos);
            oos.writeObject(this);
            //再从IO流中获取到对象
            ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois=new ObjectInputStream(bis);
            return ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

编写一个测试方法:

    public static void main(String[] args) throws CloneNotSupportedException {
       //新建一个student1
        DeepStudent student1=new DeepStudent(1,"javayz",new Teacher(1,"teacher1"));
        //student2从student1中克隆过去
        DeepStudent student2= (DeepStudent) student1.deepCopy();
        //修改基本类型 int
        student2.setId(2);
        //修改String
        student2.setName("javayz2");
        //修改对象类型teacher
        Teacher teacher = student2.getTeacher();
        teacher.setName("teacher2");
        System.out.println(student1);
        System.out.println(student2);
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是用于复制对象的两种不同方式。 浅拷贝是创建一个新对象,并将原始对象的非静态字段的值复制到新对象。新对象和原始对象的引用类型字段将引用相同的对象。换句话说,浅拷贝只复制对象的引用,而不是对象本身。 深拷贝是创建一个新对象,并将原始对象的所有字段的值复制到新对象,包括引用类型字段。这意味着在深拷贝,即使原始对象的引用类型字段引用相同的对象,新对象也将有自己的副本。 为了实现浅拷贝,可以使用`clone()`方法。这个方法是`Object`类的一个方法,需要在要复制的类实现`Cloneable`接口。然后,可以使用`clone()`方法来创建一个新对象,它将具有与原始对象相同的字段值。 要实现深拷贝,可以通过以下几种方式之一: 1. 使用序列化和反序列化:将对象写入字节流并读取回来,这将创建一个与原始对象相同但独立的新对象。 2. 使用拷贝构造函数或拷贝工厂方法:在类定义一个构造函数或静态工厂方法,它接受另一个对象作为参数,并复制其字段值到新对象。 3. 递归复制对象的所有引用类型字段:对于每个引用类型字段,创建一个新对象并复制其字段值。 需要注意的是,深拷贝可能会导致性能开销较大,尤其是在对象图很大或存在循环引用的情况下。因此,在进行深拷贝时,需要仔细考虑其对性能的影响。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个双子座的Java攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值