设计模式之原型模式(2)--深拷贝的实现图文讲解

news2025/1/16 8:05:11

目录

  • 前言
  • Clone方法
    • 复制值类型变量
    • 引用类型成员变量只复制引用
    • 浅拷贝变深拷贝
  • 示例详解
  • 注意事项
  • 总结

前言

    在上一篇原型模式博客的基础上,今天第二次写,会详细讲解一下从浅拷贝到深拷贝的实现,我也有专门写过一篇关于浅拷贝与深拷贝的文章,先将这两篇博客链接放在这里
设计模式之原型模式
深拷贝与浅拷贝,就是这么简单

Clone方法

复制值类型变量

     在Java中,clone()方法是一个用于对象复制的方法。它定义在java.lang.Object类中,可以被任何类继承和使用。

    clone()方法的作用是创建并返回一个当前对象的副本(也称为克隆)。该副本是一个独立的对象,与原始对象具有相同的状态和属性。通常情况下,克隆对象和原始对象是相互独立的,对克隆对象的修改不会影响原始对象。

    要使用clone()方法,需要满足以下两个条件:

    目标类(要进行克隆的类)必须实现Cloneable接口,否则在调用clone()方法时会抛出CloneNotSupportedException异常。

    clone()方法必须在目标类中重写,并且访问修饰符不能是私有的。重写时,可以选择调用super.clone()方法来创建副本,并适当地处理可能存在的引用类型成员变量。

下面看一段代码

class MyClass implements Cloneable {
    private int value;
    
    public MyClass(int value) {
        this.value = value;
    }
    
    public void setValue(int value) {
        this.value = value;
    }
    
    public int getValue() {
        return value;
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj1 = new MyClass(10);
        
        try {
            // 调用clone()方法创建副本
            MyClass obj2 = (MyClass) obj1.clone();
            
            System.out.println("obj1 value: " + obj1.getValue());
            System.out.println("obj2 value: " + obj2.getValue());
            
            obj2.setValue(20);
            
            System.out.println("After modifying obj2:");
            System.out.println("obj1 value: " + obj1.getValue());
            System.out.println("obj2 value: " + obj2.getValue());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

obj1 value: 10
obj2 value: 10
After modifying obj2:
obj1 value: 10
obj2 value: 20

    上面的例子,通过clone()方法创建的obj2对象与原始对象obj1具有相同的初始值。但是,在修改obj2的值后,并不会对obj1产生影响,它们是相互独立的对象。

    但是,clone()方法是浅拷贝,只复制对象的值类型成员变量,对于引用类型成员变量,仅复制引用而不复制对象本身。

引用类型成员变量只复制引用

    当类的属性是引用类型时,如果不进行深拷贝操作,拷贝对象将与原有对象共享引用。

class MyClass implements Cloneable {
    private int[] array;

    public MyClass(int[] array) {
        this.array = array;
    }

    public void setArray(int[] array) {
        this.array = array;
    }

    public int[] getArray() {
        return array;
    }

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

public class Main {
    public static void main(String[] args) {
        int[] originalArray = {1, 2, 3};
        MyClass obj1 = new MyClass(originalArray);

        try {
            MyClass obj2 = (MyClass) obj1.clone();

            System.out.println("obj1 array: " + Arrays.toString(obj1.getArray()));
            System.out.println("obj2 array: " + Arrays.toString(obj2.getArray()));

            obj2.getArray()[0] = 100;

            System.out.println("After modifying obj2:");
            System.out.println("obj1 array: " + Arrays.toString(obj1.getArray()));
            System.out.println("obj2 array: " + Arrays.toString(obj2.getArray()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

obj1 array: [1, 2, 3]
obj2 array: [1, 2, 3]
After modifying obj2:
obj1 array: [100, 2, 3]
obj2 array: [100, 2, 3]

    上面的代码可以看到,在将原始对象的数组修改后,拷贝对象的数组也被修改了。这是因为在浅拷贝中,clone()方法只复制了引用,而没有复制数组本身。因此,原始对象和拷贝对象共享同一个数组实例。

浅拷贝变深拷贝

    如果想要实现深拷贝,需要在clone()方法中对数组进行复制操作,以避免共享引用。

class MyClass implements Cloneable {
    private int[] array;

    public MyClass(int[] array) {
        this.array = array;
    }

    public void setArray(int[] array) {
        this.array = array;
    }

    public int[] getArray() {
        return array;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        MyClass cloned = (MyClass) super.clone();
        cloned.array = array.clone(); // 深拷贝数组
        return cloned;
    }
}

public class Main {
    public static void main(String[] args) {
        int[] originalArray = {1, 2, 3};
        MyClass obj1 = new MyClass(originalArray);

        try {
            MyClass obj2 = (MyClass) obj1.clone();

            System.out.println("obj1 array: " + Arrays.toString(obj1.getArray()));
            System.out.println("obj2 array: " + Arrays.toString(obj2.getArray()));

            obj2.getArray()[0] = 100;

            System.out.println("After modifying obj2:");
            System.out.println("obj1 array: " + Arrays.toString(obj1.getArray()));
            System.out.println("obj2 array: " + Arrays.toString(obj2.getArray()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

obj1 array: [1, 2, 3]
obj2 array: [1, 2, 3]
After modifying obj2:
obj1 array: [1, 2, 3]
obj2 array: [100, 2, 3]

    修改拷贝对象的数组后,原始对象的数组并没有受到影响。这是因为在深拷贝中,对数组进行了复制操作,使得原始对象和拷贝对象拥有独立的数组实例。

    如果需要实现深拷贝,也就是复制对象及其引用类型成员变量所指向的对象,就需要在clone()方法中进行相应处理。

示例详解

    如果上面的代码没有看懂,就跟着我再看一个示例,这个例子在上篇原型模式的博客中有详细讲过衍化过程,在这我只说最后一版,先看代码

//工作经验类也实现cloneable接口
public class WorkExperience implements Cloneable{
    private String timeArea;

    public String getTimeArea() {
        return timeArea;
    }

    public void setTimeArea(String timeArea) {
        this.timeArea = timeArea;
    }

    //所在公司
    private String company;

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public WorkExperience clone(){
       WorkExperience object=null;
        try {
            object=(WorkExperience) super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println("clone异常");
        }
        return object;
    }
}

//简历类
public class Resume implements Cloneable{
    private String name;
    private String sex;
    private String age;
    private WorkExperience work;//声明一个工作经历对象
    public Resume(String name){
        this.name=name;
        this.work=new WorkExperience();//实例化一个工作 经历对象
    }

    public void setPersonalInfo(String sex,String age){
        this.age=age;
        this.sex=sex;
    }

    //设置工作经历
    public void setWorkExperience(String timeArea,String company){
        this.work.setTimeArea(timeArea);
        this.work.setCompany(company);;

    }
    //展示简历
    public void display(){
        System.out.println(this.name+" " +this.sex+" "+this.age);
        System.out.println("工作经历"+this.work.getTimeArea()+" " +this.work.getCompany());
    }

    public Resume clone(){
     Resume object=null;
        try {
            object=(Resume) super.clone();
            this.work= this.work.clone();//对克隆对象里的引用也进行克隆,就达到了深复制的作用

        } catch (CloneNotSupportedException e) {
            System.err.println("clone异常");
        }
        return object;
    }

//客户端
public class Client {
    public static void main(String[] args) {
        Resume resume1=new Resume("张三");
        resume1.setPersonalInfo("男","30");
        resume1.setWorkExperience("2000-2010","Xx公司");

        Resume resume2=resume1.clone();
        resume2.setWorkExperience("2001-2011","YY集团");

        Resume resume3=resume1.clone();
        resume3.setPersonalInfo("女","35");
        resume3.setWorkExperience("2020-2023","ZZ公司");

        resume1.display();
        resume2.display();
        resume3.display();

    }
}

代码中主要有三个类
简历类、工作经验类和客户端
其中简历类中有四个属性,三个数值型和一个引用型属性-WorkExperience
在这里插入图片描述
    根据上面的介绍,如果Resume直接实现clone方法,那么只有三个数值型的属性可以直接复制出来,但是引用型的WorkExperience 只能复制引用,如下图所示,Resume是原型,Resume 2 是拷贝出来的对象,work就是WorkExperience类型的变量,所谓的复制引用,就是只把内存地址复制了一下,也就是Resume和Resume 2中的work都指向了同一块内存,那么可以想象一些,如果Resume 2修改了00110地址中的内容,原型Resume中的work的值也会发生变化,因为Resume和Resume 2 共享同一个WorkExperience对象。
在这里插入图片描述

    如果不想Resume 2改动会影响Resume ,那就需要把work所指向的对象复制一份,work里timeArea和company,都是String类型。
在这里插入图片描述

    虽然String在Java中是引用类型,但是字符串是不可变的,即创建后不能被修改。这意味着对字符串进行修改实际上是创建了一个新的字符串对象。在浅拷贝中,如果原始对象包含String类型的属性,那么在进行浅拷贝时,拷贝出来的对象会与原始对象共享同一个字符串对象的引用。由于字符串是不可变的,因此共享引用并不会导致问题,因为无法修改字符串的内容。也就是浅拷贝对于String类型的属性是有效的,所以WorkExperience也实现了Cloneable接口中的clone方法。

    接下来,就是Resume 怎么从浅拷贝到深拷贝。看下面的图
在这里插入图片描述
    图中圈住的1和2就是实现的原理–对克隆对象里的引用也进行克隆,就达到了深复制的作用
直接让WorkExperience类型的对象work进行浅复制(前面讲到的work里的string类型变量,浅拷贝对于String类型的属性有效),在Resume里复制一份新的work对象。

在这里插入图片描述

如果原型对象的引用类型属性中还包含了引用类型属性,那么需要进行递归地深拷贝来确保所有层级的引用类型属性都被复制

注意事项

在原型模式中进行深拷贝时,需要注意以下几点:

  • 引用类型属性的处理:如果原型对象中包含引用类型的属性,那么在进行深拷贝时,需要确保引用类型属性也被正确复制而不是简单的浅拷贝。否则,在拷贝对象中修改引用类型属性可能会影响到原始对象。

  • 性能和效率:深拷贝可能会消耗更多的系统资源和时间,特别是在对象结构比较复杂、对象数量较多的情况下。因此,在实现深拷贝时需要考虑性能和效率,避免不必要的资源浪费。

  • 对象图的复制:在进行深拷贝时,需要确保整个对象图都被正确地复制,即使对象之间存在相互引用或循环引用的情况,也需要能够正确地处理和复制。

  • 序列化与反序列化:使用序列化和反序列化是一种常见的实现深拷贝的方法,但需要确保对象及其所有引用类型属性都能够被序列化和反序列化,以及处理可能出现的异常情况。

  • 兼容性和可维护性:在实现深拷贝时需要考虑代码的兼容性和可维护性,确保代码易于理解和扩展,并且不会引入潜在的bug或问题。

总结

    原型模式是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过实例化类来创建。在原型模式中,深拷贝是指创建一个新对象,并且递归地复制对象及其所有引用类型属性,以确保新对象与原始对象完全独立。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1281160.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器学习】简单认识监督学习

简单认识监督学习 ⭐️Supervised learning⭐️Examples⭐️Specific example⭐️两种类型的监督学习算法🌙回归算法🌙分类算法 ⭐️总结 Hi~大家好呀!经历了暑假期间短暂的接触机器学习的一些算法,之后又对深度学习、yolo系列有些…

C++二维数组名到底代表个啥

题目先导 int a[3][4]; 则对数组元素a[i][j]正确的引用是*(*(ai)j)先翻译一下这个*(*(ai)j),即a后移i解引用,再后移j再解引用,这么看来a就应该是个二维数组,第一层存储行向量,一次解引用获得行向量的地址,…

【Leetcode题单】(01 数组篇)刷题关键点总结03【数组的改变、移动】

【Leetcode题单】(01 数组篇)刷题关键点总结03【数组的改变、移动】(3题) 数组的改变、移动453. 最小操作次数使数组元素相等 Medium665. 非递减数列 Medium283. 移动零 Easy 大家好,这里是新开的LeetCode刷题系列&…

数据库-MySQL之数据库必知必会22-26章

第 22 章 使用视图 视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。 使用视图 视图用CREATE VIEW语句来创建。 使用SHOW CREATE VIEW viewname;来查看创建视图的语句。 用DROP删除视图,其语法为DROP VIEW view…

7.24 SpringBoot项目实战【审核评论】

文章目录 前言一、编写控制器二、编写服务层三、Postman测试前言 我们在 上文 7.23 已经实现了 评论 功能,本文我们继续SpringBoot项目实战 审核评论 功能。逻辑如下: 一是判断管理员权限,关于角色权限校验 在 7.5 和 7.6 分别基于 拦截器Interceptor 和 切面AOP 都实现过…

反序列化漏洞详解(三)

目录 一、wakeup绕过 二、引用 三、session反序列化漏洞 3.1 php方式存取session格式 3.2 php_serialize方式存取session格式 3.3 php_binary方式存取session格式 3.4 代码演示 3.5 session例题获取flag 四、phar反序列化漏洞 4.1 phar常识 4.2 代码演示 4.3 phar例…

TensorRT安装及使用教程(ubuntu系统部署yolov7)

1 什么是TensorRT 一般的深度学习项目,训练时为了加快速度,会使用多 GPU 分布式训练。但在部署推理时,为了降低成本,往往使用单个 GPU 机器甚至嵌入式平台(比如 NVIDIA Jetson)进行部署,部署端也…

【Springboot+vue】如何运行springboot+vue项目

从github 或者 gitee 下载源码后,解压,再从idea打开项目 后端代码处理 这是我在gitee下载下来的源码 打开之后,先处理后端代码 该配置的配置,该部署的部署 比如将sql文件导入数据库 然后去配置文件更改配置 然后启动项目 确保…

【开源】基于JAVA语言的桃花峪滑雪场租赁系统

项目编号: S 036 ,文末获取源码。 \color{red}{项目编号:S036,文末获取源码。} 项目编号:S036,文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 游客服务2.2 雪场管理 三、数据库设…

Python、Stata、SPSS怎么学?推荐一波学习资料

1.Python学习推荐书目 关于Python机器学习,推荐学习杨维忠、张甜所著的,清华大学出版社出版的《Python机器学习原理与算法实现》,以及张甜、杨维忠所编著的,清华大学出版社出版的《Python数据科学应用从入门到精通》,…

【异常】捕获线程池执行任务时产生的异常

前言: 在编写程序时,我们为了充分利用多核CPU、加快接口响应速度,通常会使用线程池来处理请求,但线程池执行任务过程中难免会出现异常,导致请求失败。那如果我们想在任务发生异常后捕获异常,并做一些”善后…

【C++ regex】C++正则表达式

文章目录 前言一、正则表达式是什么&#xff1f;二、<regex>库的基础使用2.1 第一个示例2.1 <regex>库的函数详解std::regex_matchstd::regex_searchregex_search 和 regex_match 的区别std::regex_replacestd::regex_iterator 和 std::sregex_iterator&#xff1a…

未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序报错的解决办法

当在本地计算机上使用Microsoft Office相关库时&#xff0c;可能会出现“未在本地计算机上注册microsoft.ACE.oledb.12.0”提供程序的报错。这是由于缺少相关的驱动程序或者未安装相应的软件所导致的。下面是解决该问题的完整攻略。 可能是因为没有安装数据访问组件&#xff0…

LeetCode(51)简化路径【栈】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 简化路径 1.题目 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 / 开头&#xff09;&#xff0c;请你将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中&#xff…

Go 程序编译过程(基于 Go1.21)

版本说明 Go 1.21 官方文档 Go 语言官方文档详细阐述了 Go 语言编译器的具体执行过程&#xff0c;Go1.21 版本可以看这个&#xff1a;https://github.com/golang/go/tree/release-branch.go1.21/src/cmd/compile 大致过程如下&#xff1a; 解析 (cmd/compile/internal/synt…

【文献阅读笔记】基于自监督的异常检测和定位:SSM

2022 IEEE TRANSACTIONS ON MULTIMEDIA 领域&#xff1a;异常检测 目标&#xff1a;图像输入数据 文章目录 1、模型2、方法2.1、random masking2.2、restoration network2.3、损失函数2.4、推理时的渐进细化 3、实验4、引用5、想法 1、模型 训练&#xff1a; 每个图像实时生成随…

时间复杂度为O (nlogn)的排序算法

归并排序 归并排序遵循分治的思想&#xff1a;将原问题分解为几个规模较小但类似于原问题的子问题&#xff0c;递归地求解这些子问题&#xff0c;然后合并这些子问题的解来建立原问题的解&#xff0c;归并排序的步骤如下&#xff1a; 划分&#xff1a;分解待排序的 n 个元素的…

智能诊疗体验:整合AI技术的互联网医院小程序开发

在科技化的趋势下&#xff0c;互联网医院小程序的开发变得愈发重要&#xff0c;尤其是通过整合人工智能&#xff08;AI&#xff09;技术&#xff0c;进一步提升了就医的效率。 一、引言 互联网医院小程序其开发目标是提高医疗服务的效率&#xff0c;同时也也提升了用户的就医…

Linux学习笔记之七(shell脚本的基本语法)

Shell 1、Shell脚本2、常用运算符2、特殊语法4、关于变量的一些命令4.1、echo4.2、export4.3、read4.4、declare/typeset4.5、local4.6、unset 5、基本逻辑语法5.1、if判断5.2、for循环5.3、while循环5.4、case语句 6、函数定义7、多脚本链接 1、Shell脚本 学习shell脚本开发之…

Difference between getc(), getchar(), and gets()

getc(): 从输入中只能读单个字符 getchar()&#xff1a;从标准输入流中输入都单个字符。 两者基本等同&#xff0c;唯一不一样的是getc()是任何输入流&#xff0c;而getchar()是标准输入流。 gets:可以读入含有空格的字符串 // Example for getc() in C #include <stdio.h…