Java设计模式 —— 【创建型模式】原型模式(浅拷贝、深拷贝)详解

文章目录

  • 前言
  • 原型模式
  • 一、浅拷贝
      • 1、案例
      • 2、引用数据类型
  • 二、深拷贝
      • 1、重写clone()方法
      • 2、序列化
  • 总结


前言

先看一下传统的对象克隆方式:

原型类:

public class Student {
    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Student{'name' = " + name + "}, " + 
        		"hashCode = " + this.hashCode();
    }
}

克隆:

@Test
public void test(){
    //原型对象
    Student student = new Student("张三");

    //克隆对象
    Student student1 = new Student(student.getName());
    Student student2 = new Student(student.getName());
    Student student3 = new Student(student.getName());

    System.out.println("原型对象: " + student);
    System.out.println("克隆对象1: " + student1);
    System.out.println("克隆对象2: " + student2);
    System.out.println("克隆对象3: " + student3);
}

在这里插入图片描述

  1. 优点是比较好理解,简单易操作;
  2. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低;
  3. 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活。

原型模式

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象;
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节;
  3. 工作原理是: 通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

原型模式包含如下角色:

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

在这里插入图片描述
原型模式的克隆分为浅克隆和深克隆。

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

一、浅拷贝

1、案例

对于上文中的克隆方法加以改进:

原型类:

public class Student implements Cloneable {
    private String name;

    public Student(String name) {
        System.out.println("原型对象创建成功!!!");
        this.name = name;
    }

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

    @Override
    public String toString() {
        return "Student{'name' = " + name + "}, " + 
        		"hashCode = " + this.hashCode();
    }

    //实现对象克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("克隆成功!!!");
        return super.clone();
    }
}

测试:

@Test
public void test1() throws CloneNotSupportedException {
    Student newStudent = new Student("张三");
    Student cloneStudent = (Student) newStudent.clone();

    System.out.println("原型对象: " + newStudent);
    System.out.println("克隆对象: " + cloneStudent);
}

在这里插入图片描述

2、引用数据类型

  • 上述案例中我们可以看出克隆是克隆成功了,并且没有走构造方法,所克隆出的对象地址和原对象地址不一样,是新的对象;

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象;

  • 但是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,并没有new 一个新的对象,而是进行引用传递指向原有的引用;

  • 在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

我们添加原型类的成员变量:

School:

public class School {
    private String name;

    public School(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

Student:

public class Student implements Cloneable {
    private String name;
    private School school;

    public Student(String name, School school) {
        this.name = name;
        this.school = school;
    }

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

    public School getSchool() {
        return school;
    }

    @Override
    public String toString() {
        return "Student{'name' = " + name + ", 'school' = " + school.getName() + "}, " +
                "Student.hashCode = " + this.hashCode() + ", " +
                "name.hashCode" + name.hashCode() + ", " +
                "School.hashCode = " + school.hashCode();
    }

    //实现对象克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

测试:

@Test
public void test2() throws CloneNotSupportedException {
    Student newStudent = new Student("张三", new School("清华"));
    Student cloneStudent = (Student) newStudent.clone();

    System.out.println("原型对象:" + newStudent);
    System.out.println("克隆对象:" + cloneStudent);

    System.out.println("=====================修改克隆对象信息========================");
    cloneStudent.setName("李四");
    cloneStudent.getSchool().setName("北大");
    System.out.println("修改后的原型对象:" + newStudent);
    System.out.println("修改后的克隆对象:" + cloneStudent);
}

在这里插入图片描述
上述案例可以看出:

  • 克隆确实产生新的对象,但是引用数据类型只是进行了引用传递;
  • 以至于我们修改了cloneStudent的学校,newStudent也随之修改了;
  • 那为什么String也是引用数据类型,cloneStudent的那么由“张三”改为“李四”,而newStudent没有呢,那是因为String不可变,传入新的,当然指向新的地址了。

二、深拷贝

  1. 复制对象的所有基本数据类型的成员变量值

  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝

  3. 深拷贝实现方式有两种
    - 重写clone方法来实现深拷贝

    - 通过对象序列化实现深拷贝(推荐)

1、重写clone()方法

  • 重写clone方法主要是在原有的克隆的基础上,将引用数据类型再进行嵌套克隆;

  • 每个被引用的类也要实现Cloneable接口,重写clone()方法;

  • 这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背开闭原则。

School:

public class School implements Cloneable{
    private String name;

    public School(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

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

Student:

public class Student implements Cloneable {
    private String name;
    private School school;

    public Student(String name, School school) {
        this.name = name;
        this.school = school;
    }

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

    public School getSchool() {
        return school;
    }

    @Override
    public String toString() {
        return "Student{'name' = " + name + ", 'school' = " + school.getName() + "}, " +
                "Student.hashCode = " + this.hashCode() + ", " +
                "name.hashCode" + name.hashCode() + ", " +
                "School.hashCode = " + school.hashCode();
    }

    //实现对象克隆
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //克隆基本数据类型以及String
        Student student = (Student) super.clone();
        //引用数据类型再进行克隆
        student.school = (School) student.getSchool().clone();
        return student;
    }
}

测试:

@Test
public void test3() throws CloneNotSupportedException {
    Student newStudent = new Student("张三", new School("清华"));
    Student cloneStudent = (Student) newStudent.clone();

    System.out.println("原型对象:" + newStudent);
    System.out.println("克隆对象:" + cloneStudent);

    System.out.println("=====================修改克隆对象信息========================");
    cloneStudent.setName("李四");
    cloneStudent.getSchool().setName("北大");
    System.out.println("修改后的原型对象:" + newStudent);
    System.out.println("修改后的克隆对象:" + cloneStudent);
}

在这里插入图片描述

2、序列化

涉及到的所有类必须实现Serializable接口,否则会抛NotSerializableException异常。

School:

public class School implements Serializable{
    private String name;

    public School(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

Student:

public class Student implements Serializable {
    private String name;
    private School school;

    public Student(String name, School school) {
        this.name = name;
        this.school = school;
    }

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

    public School getSchool() {
        return school;
    }

    @Override
    public String toString() {
        return "Student{'name' = " + name + ", 'school' = " + school.getName() + "}, " +
                "Student.hashCode = " + this.hashCode() + ", " +
                "name.hashCode" + name.hashCode() + ", " +
                "School.hashCode = " + school.hashCode();
    }

    public Student deepClone() {
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Student) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (bos != null) bos.close();
                if (oos != null) oos.close();
                if (bis != null) bis.close();
                if (ois != null) ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

测试:

@Test
public void test4() throws CloneNotSupportedException {
    Student newStudent = new Student("张三", new School("清华"));
    Student cloneStudent = newStudent.deepClone();

    System.out.println("原型对象:" + newStudent);
    System.out.println("克隆对象:" + cloneStudent);

    System.out.println("=====================修改克隆对象信息========================");
    cloneStudent.setName("李四");
    cloneStudent.getSchool().setName("北大");
    System.out.println("修改后的原型对象:" + newStudent);
    System.out.println("修改后的克隆对象:" + cloneStudent);
}

在这里插入图片描述


总结

原型模式的注意事项和细节:

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态;
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码;
  4. 需要注意浅拷贝的成员变量数据类型是引用数据类型(对象)的时候;
  5. 在实现深克隆的时候可能需要比较复杂的代码建议使用序列化方式;

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

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

相关文章

Python 自动化办公的 10 大脚本

大家好,我是你们的 Python 讲师!今天我们将讨论 10 个实用的 Python 自动化办公脚本。这些脚本可以帮助你简化日常工作,提高效率。无论是处理 Excel 文件、发送邮件,还是自动化网页操作,Python 都能派上用场。 1. 批量…

设计模式学习[10]---迪米特法则+外观模式

文章目录 前言1. 迪米特法则2. 外观模式2.1 原理阐述2.2 举例说明 总结 前言 之前有写到过 依赖倒置原则,这篇博客中涉及到的迪米特法则和外观模式更像是这个依赖倒置原则的一个拓展。 设计模式的原则嘛,总归还是高内聚低耦合,下面就来阐述…

【看海的算法日记✨优选篇✨】第三回:二分之妙,寻径中道

🎬 个人主页:谁在夜里看海. 📖 个人专栏:《C系列》《Linux系列》《算法系列》 ⛰️ 一念既出,万山无阻 目录 📖一、算法思想 细节问题 📚左右临界 📚中点选择 📚…

使用Canal将MySQL数据同步到ES(Linux)

一、Canal官网文档 去到官方文档根据官网文档进行操作: QuickStart alibaba/canal Wiki GitHub 二、开启服务器中MySQL的binlog [mysqld] log-binmysql-bin # 开启 binlog binlog-formatROW # 选择 ROW 模式 server_id1 # 配置 MySQL replaction 需要定义&#x…

CENet及多模态情感计算实战

✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…

基于深度学习和卷积神经网络的乳腺癌影像自动化诊断系统(PyQt5界面+数据集+训练代码)

乳腺癌是全球女性中最常见的恶性肿瘤之一,早期准确诊断对于提高生存率具有至关重要的意义。传统的乳腺癌诊断方法依赖于放射科医生的经验,然而,由于影像分析的复杂性和人类判断的局限性,准确率和一致性仍存在挑战。近年来&#xf…

【热门主题】000074 深度学习模型:探索与应用

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 【热…

MacOS使用VSCode编写C++程序如何配置clang编译环境

前言 这段时间在练习写C和Python,用vscode这个开发工具,调试的时候遇到一些麻烦,浪费了很多时间,因此整理了这个文档。将详细的细节描述清楚,避免与我遇到同样问题的人踩坑。 1.开发环境的配置 vscode的开发环境配置…

Scala关于成绩的常规操作

score.txt中的数据: 姓名,语文,数学,英语 张伟,87,92,88 李娜,90,85,95 王强,78,90,82 赵敏,92,8…

【实战】在Koa.js中实现文件上传的接口 (本地存储)

目录 环境准备 使用 koa-body 中间件获取上传的文件 使用 Postman 测试 使用 koa-static 中间件生成图片链接 编写前端页面上传文件 文件上传是一个基本的功能,每个系统几乎都会有,比如上传图片、上传Excel等。那么在Node Koa应用中如何实现一个支持…

使用html语言完成拼多多移动端导航栏的设计-大连东软信息学院计算机科学与技术专业高级网页设计基础课题

目录 前言 一、效果图 二、图标的使用 三、代码的编写 四、运行效果 五、文档编写 前言 1.本文所讲内容来自辽宁大连东软信息学院计算机与技术专业高级网页设计(专升本)课程期中四级项目课题之一,题目要求是自主选择相应的APP移动端&…

从语法、功能、社区和使用场景来比较 Sass 和 LESS

一:可以从语法、功能、社区和使用场景来比较 Sass 和 LESS: 1:语法 原始的 Sass 采用的是缩进而不是大括号,后续的 Sass 版本与 LESS 一样使用与 CSS 类似的语法: address {.fa.fa-mobile-phone {margin: 0 3px 0 2…

7. 现代卷积神经网络

文章目录 7.1. 深度卷积神经网络(AlexNet)7.2. 使用块的网络(VGG)7.3. 网络中的网络(NiN)7.4. 含并行连结的网络(GoogLeNet)7.5. 批量规范化7.5.1. 训练深层网络7.5.2. 批量规范化层…

sqlmap详细使用

SQLmap使用详解 SQLmap(常规)使用步骤 1、查询注入点 python sqlmap.py -u http://127.0.0.1/sqli-labs/Less-1/?id12、查询所有数据库 python sqlmap.py -u http://127.0.0.1/sqli-labs/Less-1/?id1 --dbs3、查询当前数据库 python sqlmap.py -u htt…

React+TS+css in js 练习

今天分享的内容是动态规划的经典问题--0-1 背包问题 0-1背包问题的描述如下:给定一组物品,每种物品都有自己的重量和价值,背包的总容量是固定的。我们需要从这些物品中挑选一部分,使得背包内物品的总价值最大,同时不超过背包的总容量。 举个例子:假设这组物品的质量…

刷题日常(找到字符串中所有字母异位词,​ 和为 K 的子数组​,​ 滑动窗口最大值​,全排列)

找到字符串中所有字母异位词 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 题目分析: 1.将p里面的字符先丢进一个hash1中,只需要在S字符里面找到多少个和他相同的has…

《C++ Primer Plus》学习笔记|第8章 函数探幽 (24-11-30更新)

文章目录 8.1 内联函数8.2 引用变量8.2.1 创建引用变量8.2.2 将引用用作函数参数8.2.3 引用的属性和特别之处特点1:在计算过程中,传入的形参的值也被改变了。特点2:使用引用的函数参数只接受变量,而不接受变量与数值的运算左值引用…

[2024年1月28日]第15届蓝桥杯青少组stema选拔赛C++中高级(第二子卷、编程题(1))

参考程序&#xff1a; #include <iostream> #include <algorithm> // 用于 std::sortusing namespace std;int main() {int a, b, c;cin >> a >> b >> c;// 将三个数放入一个数组中int arr[3] {a, b, c};// 对数组进行排序sort(arr, arr 3);…

基于hexo框架的博客搭建流程

这篇博文讲一讲hexo博客的搭建及文章管理&#xff0c;也算是我对于暑假的一个交代 &#xff01;&#xff01;&#xff01;注意&#xff1a;下面的操作是基于你已经安装了node.js和git的前提下进行的&#xff0c;并且拥有github账号 创建一个blog目录 在磁盘任意位置创建一个…

基于Java Springboot传统戏曲推广微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信…