复制一个类的正确姿势

本文深入探讨了Java中对象拷贝的问题,通过实例展示了如何使用Get/Set方法、浅拷贝和深拷贝实现对象复制。在讲解过程中,提到了对象引用和内存分配的概念,并提供了代码示例来解释错误的拷贝结果。最后,通过实现`Cloneable`接口和序列化方法解决了深拷贝问题,确保了复杂对象(如含有引用类型的对象)能够被正确复制。
摘要由CSDN通过智能技术生成

复制一个类的正确姿势

引入

在开发中我们常常遇到需要拷贝一个对象的情况,比如我所给出的例子;
现在我们有一个Student对象的模板,该对象包含姓名、性别和学校三个基本属性,其中学校属性已赋值,现要基于这个Stu对象模板进行一些额外的赋值操作分别得到Stu1和Stu2,接下来看操作
Student对象


/**
 * @author CaveWang
 * @date 2022/10/24 22:41
 */
public class Student {
    private String NAME;
    private String SEX;
    private String SCHOOL;

    public String getNAME() {
        return NAME;
    }

    public void setNAME(String NAME) {
        this.NAME = NAME;
    }

    public String getSEX() {
        return SEX;
    }

    public void setSEX(String SEX) {
        this.SEX = SEX;
    }

    public String getSCHOOL() {
        return SCHOOL;
    }

    public void setSCHOOL(String SCHOOL) {
        this.SCHOOL = SCHOOL;
    }

    @Override
    public String toString() {
        return "Student{" +
                "NAME='" + NAME + '\'' +
                ", SEX='" + SEX + '\'' +
                ", SCHOOL='" + SCHOOL + '\'' +
                '}';
    }
}

测试

/**
 * @author CaveWang
 * @date 2022/10/24 22:42
 */
public class CloneTest {
    public static void main(String[] args) {
        //一个学生对象模板
        Student student = new Student();
        student.setSCHOOL("瓦罗兰大陆第三高级中学");
        //基于学生模板进行修改得到stu1和stu2
        Student student1 = CloneTest.method1(student);
        Student student2 = CloneTest.method2(student);
        System.out.println("student"+student);
        System.out.println("student1"+student1);
        System.out.println("student2"+student2);
    }

    public static Student method1(Student student){
        student.setNAME("MoMo");
        student.setSEX("female");
        return student;
    }

    public static Student method2(Student student){
        student.setNAME("Cave");
        student.setSEX("male");
        return student;
    }
}

不出意外的话就要出意外了,或许许多基础不牢固的同学都会以为输出是这样的
错误的输出如下所示!

studentStudent{NAME=null, SEX=null, SCHOOL='瓦罗兰大陆第三高级中学'}
student1Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学'}
student2Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}

正确的输出如下所示!

studentStudent{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}
student1Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}
student2Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}

何解?在于我们只对原对象进行了赋值操作,当创建Stu对象时,它在堆内存中开辟了一块空间存储对象的内容。而当Stu1和Stu2通过method赋值时,Stu1和Stu2并不会再重新开辟一块堆内存,而是将Stu内存空间存储的对象的地址给到了Stu1和Stu2,这个地址存在栈内存中,通过栈内存的地址找到堆内存里对象的内容,Stu、Stu1、Stu2共用就完事了。所以说, 三个对象指向的都是同一块内容,不管谁改了对象的内容,另外两个再访问都是改过之后的了。
地址相同

解决

这里提供三种解决方案

Get/Set

最简单粗暴的方式,重新定义一个对象进行Get和Set操作

		Student student1 = new Student();
        student1.setSCHOOL(student.getSCHOOL());
        student1.setNAME("MoMo");
        student1.setSEX("female");
        Student student2 = new Student();
        student2.setSCHOOL(student.getSCHOOL());
        student2.setNAME("Cave");
        student2.setSEX("male");
        System.out.println("student"+student); //studentStudent{NAME='null', SEX='null', SCHOOL='瓦罗兰大陆第三高级中学'}
        System.out.println("student1"+student1);//student1Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学'}
        System.out.println("student2"+student2);//student2Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学'}

浅拷贝

我自认为浅拷贝是指拷贝时只拷贝对象本身,包括对象中的基本变量,比如Stu类中定义的姓名性别和学校名,而不拷贝对象包含的引用所指向的对象
修改下Stu类,实现克隆接口,继承克隆方法

/**
 * @author CaveWang
 * @date 2022/10/24 22:41
 */
public class Student implements Cloneable{
    private String NAME;
    private String SEX;
    private String SCHOOL;

    public String getNAME() {
        return NAME;
    }

    public void setNAME(String NAME) {
        this.NAME = NAME;
    }

    public String getSEX() {
        return SEX;
    }

    public void setSEX(String SEX) {
        this.SEX = SEX;
    }

    public String getSCHOOL() {
        return SCHOOL;
    }

    public void setSCHOOL(String SCHOOL) {
        this.SCHOOL = SCHOOL;
    }

    @Override
    public String toString() {
        return "Student{" +
                "NAME='" + NAME + '\'' +
                ", SEX='" + SEX + '\'' +
                ", SCHOOL='" + SCHOOL + '\'' +
                '}';
    }

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

然后克隆对象再输出,发现基本变量是没有问题的。

	Student student = new Student();
    student.setSCHOOL("瓦罗兰大陆第三高级中学");
	Student student1 = (Student) student.clone();
    student1.setNAME("MoMo");
    student1.setSEX("female");
    Student student2 = (Student) student.clone();
    student2.setNAME("Cave");
    student2.setSEX("male");
    System.out.println("student"+student);
    System.out.println("student1"+student1);
    System.out.println("student2"+student2);

在这里插入图片描述
如果此时在Stu中再加入一个引用类型呢?
创建Subject课程类,其中有课程名和分数两个字段

/**
 * @author CaveWang
 * @date 2022/10/25 23:14
 */
public class Subjects {
    private String name;
    private String score;

    public Subjects(String name, String score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public String getScore() {
        return score;
    }

    public void setScore(String score) {
        this.score = score;
    }

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

然后在Stu类中添加课程字段,该字段为引用类型

public class Student implements Cloneable{
    private String NAME;
    private String SEX;
    private String SCHOOL;
    private Subjects subjects;

    public Student(String NAME, String SEX, String SCHOOL, Subjects subjects) {
        this.NAME = NAME;
        this.SEX = SEX;
        this.SCHOOL = SCHOOL;
        this.subjects = subjects;
    }

    public void setSubjectsScore(String score) {
        this.subjects.setScore(score);
    }

    public void setSubjectsName(String name) {
        this.subjects.setName(name);
    }

    public String getNAME() {
        return NAME;
    }

    public void setNAME(String NAME) {
        this.NAME = NAME;
    }

    public String getSEX() {
        return SEX;
    }

    public void setSEX(String SEX) {
        this.SEX = SEX;
    }

    public String getSCHOOL() {
        return SCHOOL;
    }

    public void setSCHOOL(String SCHOOL) {
        this.SCHOOL = SCHOOL;
    }

    @Override
    public String toString() {
        return "Student{" +
                "NAME='" + NAME + '\'' +
                ", SEX='" + SEX + '\'' +
                ", SCHOOL='" + SCHOOL + '\'' +
                ", subjects=" + subjects +
                '}';
    }

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

测试类:

/**
 * @author CaveWang
 * @date 2022/10/24 22:42
 */
public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        //一个学生对象模板
        Student student = new Student(null,null,"瓦罗兰大陆第三高级中学",new Subjects("数据结构","80"));
        Student student1 = (Student) student.clone();
        student1.setNAME("MoMo");
        student1.setSEX("female");
        //修改课程名和分数
        student1.setSubjectsName("数字图像处理");
        student1.setSubjectsScore("90");
        Student student2 = (Student) student.clone();
        student2.setNAME("Cave");
        student2.setSEX("male");
        //修改课程名和分数
        student2.setSubjectsName("二维游戏开发");
        student2.setSubjectsScore("95");
        System.out.println("student="+student);
        System.out.println("student1="+student1);
        System.out.println("student2="+student2);
    }

    public static Student method1(Student student){
        student.setNAME("MoMo");
        student.setSEX("female");
        return student;
    }

    public static Student method2(Student student){
        student.setNAME("Cave");
        student.setSEX("male");
        return student;
    }
}

输出:

student=Student{NAME='null', SEX='null', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}
student1=Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}
student2=Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}

从最后对studnet的输出可以看到,student、studnet1 和 studnet2 的 subject 对象,实际上还是指向了统一个对象,只对对它的引用进行了传递。

深拷贝

实现深拷贝常见有两种方式,序列化和重写clone()方法,本文主要介绍clone方法。
继续改写上述代码,让subject类也继承Cloneable接口

public class Subjects implements Cloneable{
    private String name;
    private String score;

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

然后最重要的部分来了,在student的clone()方法中也将subject进行克隆

public class Student implements Cloneable{
    private String NAME;
    private String SEX;
    private String SCHOOL;
    private Subjects subjects;

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student student = (Student)super.clone();
        student.subjects = (Subjects) this.subjects.clone();
        return student;
    }
    ...
}    

此时再看输出,大功告成!

student=Student{NAME='null', SEX='null', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='数据结构', score='80'}}
student1=Student{NAME='MoMo', SEX='female', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='数字图像处理', score='90'}}
student2=Student{NAME='Cave', SEX='male', SCHOOL='瓦罗兰大陆第三高级中学', subjects=Subjects{name='二维游戏开发', score='95'}}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值