当前位置: 首页 > article >正文

【Java 学习】对象赋值的艺术:Java中clone方法的浅拷贝与深拷贝解析,教你如何在Java中实现完美复制

💬 欢迎讨论:如对文章内容有疑问或见解,欢迎在评论区留言,我需要您的帮助!

👍 点赞、收藏与分享:如果这篇文章对您有所帮助,请不吝点赞、收藏或分享,谢谢您的支持!

🚀 传播技术之美:期待您将这篇文章推荐给更多对需要学习Java语言、低代码开发感兴趣的朋友,让我们共同学习、成长!


# 1. 什么是自定义类型的赋值?
什么是赋值呢?

```java
	// 创建一个变量a,值为10
        int a = 10;
        // 把a的值赋值给b
        int b = a;
        
        // 他们两个的值一样
        System.out.println("a: "+a);
        System.out.println("b: "+ b);

在这里插入图片描述

int是Java中的内置类型,我们自己创建的自定义类型(类)可以赋值吗?

同学们看一下,判断这是我们自定义类型的赋值吗?

class Student{
    public String name;
    public int age;

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

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

public class Main {
    public static void main(String[] args){
        // 创建一个对象
        Student s1 = new Student("李华",18);
        // 把 s1的值赋值给s2
        Student s2 = s1;

        System.out.println(s1.toString());
        System.out.println(s2.toString());
    }
}

答: 不是的,在上面的代码中,s1s2指向的是一个空间,s2并没有属于自己的空间。

如图:
在这里插入图片描述

我们想要的是:s2有自己的空间,并且空间的内容和s1一样(如下图)
在这里插入图片描述
自定义类型的赋值是:申请一个自己单独能管理的空间

想要完成 “完美” 的赋值,就需要使用clone()方法

2. clone 方法

2.1 接口Cloneable 和 Object的中的clone方法

Cloneable 接口:
只有实现了 Cloneable 接口的类才可以正常调用 clone() 方法。否则会抛出 CloneNotSupportedException 异常

Cloneable接口文档如下
在这里插入图片描述

重写 clone() 方法:
Object 类的 clone() 方法是 protected,所以在自定义类中重写时,必须将其访问修饰符改为 public,才能从外部访问

Object类中的Clone方法:
在这里插入图片描述

总结
当一个类需要写clone方法时,必须实现接口Cloneable,并且重写Object类中的clone方法

如果想要更详细的了解Object类中的Clone方法,可以参考 Object 类

2.2 用clone 方法解决标题1的问题

标题1的问题很严重,因为两个引用类型都指向一个空间,那么,当其中一个对象修改属性时另一个对象的属性也会被修改

public class Main {
    public static void main(String[] args){
        // 创建一个对象
        Student s1 = new Student("李华",18);
        // 把 s1的值赋值给s2
        Student s2 = s1;

        System.out.println(s1.toString());
        System.out.println(s2.toString());

        // 修改s1的name,s2的name也修改了
        s1.name = "王小明";
        System.out.println("只修改s1的name为 王小明");
        
        System.out.println(s1.toString());
        System.out.println(s2.toString());
    }
}

在这里插入图片描述
我们要做的是,进行赋值时给s2也开一个空间
在这里插入图片描述

需要在让Student类实现Cloneable接口,重写Object中的Clone方法,并且调Clone方法。

class Student implements Cloneable{
    public String name;
    public int age;

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

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

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

public class Main {
    public static void main(String[] args){


        try {
            // 创建一个对象
            Student s1 = new Student("李华",18);
            // 把 s1的值赋值给s2
            Student s2 = s1.clone(); // 调用object中的clone方法

            System.out.println(s1.toString());
            System.out.println(s2.toString());

            // 修改s1的name,s2的name也修改了
            s1.name = "王小明";
            System.out.println("只修改s1的name为 王小明");

            System.out.println(s1.toString());
            System.out.println(s2.toString());

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

在修改s1name时,s2就不会随着s1而变动了。
在这里插入图片描述

2.3 Object类中clone方法的定义和用途

Object 类中的 clone() 方法用于创建当前对象的一个副本(即对象的浅拷贝)。它是一个受保护的方法(用protected修饰),所以默认情况下只有 Object 类本身及其子类可以调用。在实际开发中,clone() 方法常常被重写,以便提供更灵活的复制功能

clone方法在object类的声明如下:

protected Object clone() throws CloneNotSupportedException;

想要深入的了解Object和clone方法请点击:Java的生命之源:走进Object类的神秘花园,解密Object类的背后故事

说明

  • 返回值类型:Object 类型。返回的是当前对象的副本,因此返回值需要被强制转换为具体的类型。
  • 异常:CloneNotSupportedException,如果对象的类没有实现 Cloneable 接口,调用 clone() 方法时会抛出此异常。

clone() 方法是 Object 类中定义的,用于返回一个与当前对象相同的副本。在默认实现中,它是浅拷贝意味着它会复制对象的基本数据类型字段,但如果对象包含引用类型的字段,那么这些字段依然指向原来的对象

3. 浅拷贝

浅拷贝指的是在复制对象时,只复制对象的基本数据类型字段和引用字段的引用(即内存地址),并没有复制引用字段所指向的对象本身。也就是说,原对象和拷贝对象会共享对引用类型字段(例如数组、集合等)的引用。这就导致了如果你修改原对象中的引用类型字段,拷贝对象中的相同字段也会被修改。

在上述的例子中,super.clone() 方法通过 Object 类的 clone() 方法创建一个新的 Student 对象。

String name = “***”:这种方式使用字符串字面量创建字符串

String name = new String(“***”):这种方式会在堆内存中创建一个新的 String 对象,其内容为 “***”,当name的内容改变时,会再创建一个空间,然后把新的引用(空间地址)赋值给name。

由于 name(name=“***”) 和 age 都是基本数据类型和不可变对象(如 String),拷贝的过程中,name 和 age 会被直接复制。

对于 name 字段,String 是不可变的,所以即使修改原对象的 name 字段,拷贝对象的 name 字段也不会受到影响。

但是,当我们在Studnet类中提添加一个自定义的引用类型,再次仿照上述Main()类中的main方法操作,发生什么呢?

class Person{
    public int number; // 电话号码

    public Person(int number){
        this.number = number;
    }
}

class Student implements Cloneable{
    public String name;
    public int age;
    Person person;

    public Student(String name, int age,int number){
        person = new Person(number);
        this.name = name;
        this.age = age;
    }

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

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



public class Main {
    public static void main(String[] args){


        try {
            // 创建一个对象
            Student s1 = new Student("李华",18,111);
            // 把 s1的值赋值给s2
            Student s2 = s1.clone();

            System.out.println(s1.toString());
            System.out.println(s2.toString());

            // 修改s1的号码为888
            s1.person.number = 888;
            System.out.println("只修改s1的号码为 888");

            System.out.println(s1.toString());
            System.out.println(s2.toString());

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

使用clone后,s1s2不是指向各自自己的空间了吗,为什么改变s1中的值而s2中的值也发生改变了呢?

这就是浅拷贝,浅拷贝指的是在复制对象时,只复制对象的基本数据类型字段和引用字段的引用(即内存地址),并没有复制引用字段所指向的对象本身

Studnet类中,用一个引用变量person,创建s1person指向自己的空间,person存储的是引用(空间地址,比如是0X555),当把s1克隆给s2s2中的person存储也是0X555
在这里插入图片描述
为了解决这一问题,需要自己重写clone方法,并且手动的给person开空间。

4. 深拷贝

4.1 什么是深拷贝?

简单的来说,深拷贝就是解决浅拷贝存在的问题,为对象中的引用类型也开辟自己的空间,把类的赋值完美化。

在这里插入图片描述

4.2 解决浅拷贝遗留的问题

为了解决浅拷贝遗留的问题,需要在Person类中也写一个clone方法:

// 实现接口
class Person implements Cloneable{
    public int number; // 电话号码

    public Person(int number){
        this.number = number;
    }
    
    // 重写写clone方法
    public Person clone() throws CloneNotSupportedException{
        return (Person)super.clone(); // 调用clone方法
    }
    
}

需要在Student中改进clone方法的定义,使用Person中的clone方法:

    public Student clone() throws CloneNotSupportedException{
        // 创建一个Studnet临时对象 tmp
        // 此时的s.person 和 this.person 存储的相同的引用
        Student tmp = (Student)super.clone();

        // 使用person的clone方法创建一个新的对象
        // s.person 指向的是新的对象
        tmp.person = person.clone();


        return tmp;
    }

改为深拷贝的整体代码:

class Person implements Cloneable{
    public int number; // 电话号码

    public Person(int number){
        this.number = number;
    }

    public Person clone() throws CloneNotSupportedException{
        return (Person)super.clone();
    }

}

class Student implements Cloneable{
    public String name;
    public int age;
    Person person;

    public Student(String name, int age,int number){
        person = new Person(number);
        this.name = name;
        this.age = age;
    }

    @Override
    public Student clone() throws CloneNotSupportedException{
        // 创建一个Studnet临时对象 tmp
        // 此时的s.person 和 this.person 存储的相同的引用
        Student tmp = (Student)super.clone();

        // 使用person的clone方法创建一个新的对象
        // s.person 指向的是新的对象
        tmp.person = person.clone();

        return tmp;
    }

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



public class Main {
    public static void main(String[] args){


        try {
            // 创建一个对象
            Student s1 = new Student("李华",18,111);
            // 把 s1的值赋值给s2
            Student s2 = s1.clone();

            System.out.println(s1.toString());
            System.out.println(s2.toString());

            // 修改s1的号码为888
            s1.person.number = 888;
            System.out.println("只修改s1的号码为 888");

            System.out.println(s1.toString());
            System.out.println(s2.toString());

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

运行程序:
在这里插入图片描述
显然,当s1 改变时s2不会改变,s1中的引用类型persons2中的person指向不同的空间。

在这里插入图片描述

4.3 总结

关键点
深拷贝的核心是递归地复制引用类型字段所指向的对象,而不仅仅是复制它们的引用
浅拷贝会让原对象和拷贝对象共享引用类型字段,而 深拷贝 会确保原对象和拷贝对象的引用类型字段相互独立。

在上面的例子中,Student 类中的 person 字段是通过 clone() 方法进行深拷贝的,修改原对象的 person 字段不会影响拷贝对象。

深拷贝的应用
独立性:深拷贝常用于确保原对象和拷贝对象在引用类型字段上相互独立,避免它们相互影响。

内存管理:在某些场景下,深拷贝可以避免对共享资源的修改导致意外的副作用。

总结
深拷贝是对象复制中的一种高级技术,能够确保对象之间完全独立,特别适用于包含引用类型字段的复杂对象。当需要确保每个对象的字段都能被独立地复制,并且修改一个对象不会影响另一个对象时,深拷贝是必不可少


http://www.kler.cn/a/472717.html

相关文章:

  • kubernetes第五天
  • 【大模型】百度千帆大模型对接LangChain使用详解
  • el-table 多级表头
  • 用豆包MarsCode IDE打造精美数据大屏:从零开始的指南
  • LangChain速成课程_构建基于OpenAI_LLM的应用
  • unity3d-搞个场景漫游如何实现Alpha
  • 【超详细】MIT 液态神经网络(LNNs)——深度学习新动向
  • ubuntu18升级至ubuntu20
  • wps宏js接入AI功能和接入翻译功能
  • Aviatrix Controller 未授权命令注入漏洞复现(CVE-2024-50603)
  • JAVA XXE 学习总结
  • C 语言奇幻之旅 - 第16篇:C 语言项目实战
  • 安装 华三云实验室 H3C Cloud Lab
  • Android Studio 安装配置(个人笔记)
  • 【杂记】机器视觉 #opencv #numpy #matplotlib
  • 自闭症家庭:建立支持系统与平衡生活
  • QML学习(九) Qt Quick - Control2中的主要组件:Label组件和Button组件的属性和使用方法
  • 机器学习模型评估指标
  • 页面转 PDF 功能的实现思路与使用方法
  • 【算法】八大排序算法
  • PostgreSQL 插件的事务回调机制
  • 怎么把word试题转成excel?
  • 在windows系统上安装docker并自定义安装和数据存储位置
  • No Homebrew ruby 2.6.3_2 available for arm64 processors!
  • 微软Office存在的意义是什么?
  • 【深度学习量化交易12】基于miniQMT的量化交易框架总体构建思路——回测、模拟、实盘通吃的系统架构