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

目录

  • 前言
  • 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.mfbz.cn/a/214460.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

超大规模集成电路设计----MOS器件原理(二)

本文仅供学习,不作任何商业用途,严禁转载。绝大部分资料来自----数字集成电路——电路、系统与设计(第二版)及中国科学院段成华教授PPT 超大规模集成电路设计----MOS器件原理(二) 半导体物理知识补充介绍1. 半导体材料2. 固体类型…

Docker快速创建一个单机版的Jenkins实例

谈到 CI/CD,那便少不了这里面的佼佼者 Jenkins,正如 Jenkins 官网说的一样:“Build great things at any scale”,构建伟大,无所不能! 话不多说,该篇文章将会带你使用 Docker 快速创建一个单机…

计算机网络(一)| 概述 因特网 性能 协议基本

文章目录 1. 因特网组成1.1 四元素组成1.2 二元素组成1.3 核心部分 2.计算机网路的功能3. 几种不同类别的网络4 性能指标5 网路协议5.1网络体系结构 6 PDU 互联网(或因特网)之所以能够向用户提供服务,是因为互联网具有两个重要基本特点 连通性…

Siemens-NXUG二次开发-C/C++/Python环境配置[20231204]

Siemens-NXUG二次开发-C/C/Python运行方式[20231204] 1.NX/UG C/C/Python API官方开发文档2.运行方式2.1内部模式2.2 外部模式2.3 许可证书服务器启动 3.C/C环境配置4.Python环境配置5.第三方环境配置 1.NX/UG C/C/Python API官方开发文档 西门子NX/UG Python api开发文档&…

C++ day48 打家劫舍

题目1:198 打家劫舍 题目链接:打家劫舍 对题目的理解 专业小偷偷盗房屋的钱财,每个房屋存放的金额用非负整数数组表示; 如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警; 不触动警报装置的情况…

简单3D姿态基线模型网络架构与验证【SIM】

在这篇文章中,我们将回顾 ICCV’17 上提出的 Simple 3D Pose Baseline ,即用于 3d 人体姿势估计的简单而有效的基线,也称为 SIM。 NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在…

Pandas进阶:拼接 concat 使用方法

1.处理索引和轴 假设我们有2个关于考试成绩的数据集。 df1 pd.DataFrame({ name:[A,B,C,D],math:[60,89,82,70],physics:[66, 95,83,66],chemi…

Siemens-NXUG二次开发-新建与保存prt文件[Python UF][20231204]

Siemens-NXUG二次开发-新建与保存prt文件[Python UF][20231204] 1.python uf函数1.1 NXOpen.UF.Part.New1.2 NXOpen.UF.Part.Save1.3 NXOpen.UF.Ui.OpenListingWindow1.4 NXOpen.UF.Ui.IsListingWindowOpen1.5 NXOpen.UF.Ui.WriteListingWindow1.6 NXOpen.UF.Ui.SaveListingWin…

Spring MVC学习随笔-文件下载和上传(配置文件上传解析器multipartResolver)

学习视频:孙哥说SpringMVC:结合Thymeleaf,重塑你的MVC世界!|前所未有的Web开发探索之旅 学习视频:【编程不良人】继spring之后快速入门springmvc,面对SpringMVC不用慌 六、SpringMVC 文件上传下载 6.1 文件…

LeetCode(49)用最少数量的箭引爆气球【区间】【中等】

目录 1.题目2.答案3.提交结果截图 链接: 用最少数量的箭引爆气球 1.题目 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] [x_start, x_end] 表示水平直径在 x_start 和 x_end之间的气球。你不知道气…

【WPF.NET开发】创建简单WPF应用

本文内容 先决条件什么是 WPF?配置 IDE创建项目设计用户界面 (UI)调试并测试应用程序 通过本文你将熟悉在使用 Visual Studio 开发应用程序时可使用的许多工具、对话框和设计器。 你将创建“Hello, World”应用程序、设计 UI、添加代码并调试错误。在此期间&#…

leetcode 142.环形链表2

我来更新 leetcode 题目了,接着上一次,这一次是上一道题目的提升(有点数学题的感觉) 142.环形链表2 题目 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表…

CCKS2023-面向上市公司主营业务的实体链接评测-亚军方案

赛题分析 大赛地址 https://tianchi.aliyun.com/competition/entrance/532097/information 任务描述 本次任务主要针对上市公司的主营业务进行产品实体链接。需要获得主营业务中的产品实体,将该实体链接到产品数据库中的某一个标准产品实体。产品数据库将发布在竞赛…

RK3568平台开发系列讲解(Linux系统篇) dtb 到 device_node 的转化

🚀返回专栏总目录 文章目录 一、dtb 展开流程二、dtb 解析过程源码分析沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍通过设备树 dtb 如何展开成 device_node 一、dtb 展开流程 设备树源文件编写: 根据设备树的基本语法和相关知识编写符合规范的设备树。…

工具类整理

常用工具类 在java的庞大体系中,其实有很多不错的小工具,也就是我们平常说的:轮子。 CollectionUtils 目前比较主流的是spring的org.springframework.util包下的CollectionUtils工具类。和apache的org.apache.commons.collections包下的Co…

根据豆瓣对《流浪地球》的短评数据进行文本分析和挖掘

1背景 2019年2月5日电影《流浪地球》正式在中国内地上映。该电影在举行首映的时候,口德好得出奇,所有去看片的业界大咖都发出了画样赞叹,文化学者能锦说:“中国科幻电影元年开启了。"导演徐峰则说,“里程碑式的电影&#xf…

实时流式计算 kafkaStream

文章目录 实时流式计算Kafka StreamKafka Streams 的关键概念KStreamKafka Stream入门案例编写SpringBoot 集成 Kafka Stream 实时流式计算 一般流式计算会与批量计算相比较 流式计算就相当于上图的右侧扶梯,是可以源源不断的产生数据,源源不断的接收数…

WEB服务器配置与HTTP分析

目录 实验目的: 实验要求: 实验原理: 实验步骤: 步骤1:创建拓扑 步骤2:为PC、Client和Server配置IPv4地址、子网掩码和域名服务器 步骤3:启动设备和服务器 步骤4:测试PC-1、C…

【Qt开发流程】之自定义语法高亮和使用HTML语法

描述 语法高亮(Syntax Highlighting)是一种在编辑器中突出显示代码语法元素的技术,使其更易于阅读和理解。 Qt提供了一个功能齐全的语法高亮框架,支持多种语言和格式,可以自定义颜色和样式。 对于使用Qt的开发人员来说…

HADOOP::Fsimage和Edits解析

NameNode被格式化之后,将在/opt/module hadoop-3.1.3/data/tmp/dfs/name/curent目录 中产生如下文件 fsimage_ 0000000000000000000 fsimage_ 0000000000000000000.md5 seen_txid VERSION (1) Fsimage文件: HDFS文件系统元数据的一个永久性的检查点&#xff0…
最新文章