java设计模式----原型模式,深拷贝,浅拷贝,序列化,反序列化

主文章(所有java设计模式的目录)
https://blog.csdn.net/grd_java/article/details/122252696
源码位置
码云:https://gitee.com/yin_zhipeng/design_mode.git
GitHub:
学习设计模式前必须知道的东西
  1. 看待设计模式,要站在更大的角度(代码重用性、可读性、可扩展性、可靠性、程序高内聚,低耦合)来综合考虑看待,而不是功能实现的角度看待,不要觉得实现一个功能没必要这么麻烦
  1. 文章中给出的设计模式类图都是标准的实现方式,并不一定要完全遵守标准,所以只要设计思想符合,一个设计模式有多种实现方式,尤其是看别人源码的时候,不要用标准类图死扣

1. 原型模式(Prototype Pattern)

思考一个问题
  1. 现在有一只羊类(sheep类),有姓名tom,年龄1,颜色白色,这三个属性,编写程序创建和tom羊属性完全相同的10只羊
传统方法实现代码:com/yzpnb/design_mode/prototype_pattern/tradition包下
  1. 创建Sheep类
    在这里插入图片描述
  2. 复制10个
    在这里插入图片描述
  1. 优缺点
  1. 优点仅仅是比较好理解,简单易操作,但是冗余度高
  2. 在创建新对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
  3. 总是需要初始化(new)对象,而不是动态地获取对象运行时的状态,不够灵活
如何改进呢:代码在:com/yzpnb/design_mode/prototype_pattern/improved
  1. java中Object类,提供了clone()方法,可以将一个java对象复制一份,但是需要实现clone的java类必须实现Cloneable接口,该接口表示该类能够复制且具有复制能力====》原型模式
  2. 原型模式:
  1. 用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()
  4. 就像孙悟空,拔出自己身上的毛,变出其它猴子
  1. 类图
    在这里插入图片描述

1. 原型模式之浅拷贝

  1. 浅拷贝就是基本数据类型的成员变量,直接值传递,将属性复制一份给新对象
  2. 引用类型,例如数组,类对象等,只会进行引用传递,也就是将内存地址复制一份给新对象
  3. 当一个对象改变引用类型的变量时,其它对象都会受到影响
  4. 接下来我们介绍的克隆羊就是浅拷贝
  5. 通过默认的clone方法实现(sheep = (Sheep)super.clone();
  1. Sheep类继承Cloneable接口,实现clone()方法,调用Object类clone方法实现拷贝
    在这里插入图片描述
    在这里插入图片描述
  2. 调用clone方法,获取拷贝对象(浅拷贝)
    在这里插入图片描述
为什么说它是浅拷贝呢?
  1. 我们给Sheep加一个引用对象friend,表示他俩的朋友
    在这里插入图片描述
  2. 测试克隆后两个对象的朋友是否是一个对象(浅拷贝就是一个对象,否则朋友也会克隆一个)
    在这里插入图片描述
可见,这是浅拷贝,如果是深拷贝,朋友也应该拷贝一份,而这里只是将引用复制了过去

2. 深拷贝

  1. 克隆所有基本数据类型成员变量的值
  2. 为所有引用数据类型变量申请空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是深拷贝,对整个对象拷贝,刨根问底的克隆所有
  1. 实现方式1:重写clone方法实现深拷贝----人肉搜索
  1. 就是自己找到所有引用类型对象,统统继承Cloneable 实现clone方法
  2. 然后自己的clone方法中,把所有引用对象,统统人肉调用clone方法克隆
  1. 实现方式2:通过对象序列化实现深拷贝
  1. 序列化后,反序列化就完事了
1. 重写clone实现
实现方式一重写clone()实现:代码:com/yzpnb/design_mode/deepclone包下
  1. 搞一个专门作为引用对象的类,也需要实现clone(),如果没有引用类型,直接实现浅拷贝即可
    在这里插入图片描述
  2. 搞一个我们需要深拷贝引用类型的类,重写clone方法,先浅拷贝自己(记为P1),然后手动浅拷贝自己引用的对象(记为C1、C2…),将拷贝后的自己引用的对象,赋值给自己(C1、C2…赋值给P1),深拷贝完成
    在这里插入图片描述
  3. 测试
    在这里插入图片描述
2. 通过反序列化实现
实现方式二反序列化实现:代码:com/yzpnb/design_mode/deepclone包下
  1. 搞一个专门作为引用对象的类,继承序列化接口,指定序列id
    在这里插入图片描述
  2. 搞一个我们需要深拷贝引用类型的类,同样需要实现序列化接口,然后编写克隆方法,进行序列化,然后反序列化克隆对象
    在这里插入图片描述
    在这里插入图片描述
  3. 测试
    在这里插入图片描述

3. 源码

Spring源码中,用到了原形模式
  1. 测试Spring源码和UML类图全部放在SpringSource模块中
    在这里插入图片描述
  1. Spring中原形bean的创建,就有原型模式的应用
    在这里插入图片描述
  1. 准备一个bean对象
    在这里插入图片描述
  2. beans.xml
    在这里插入图片描述
  3. ProtoType.java 类,获取两个bean实例,查看是否一样
    在这里插入图片描述
  4. 进入getBean方法查看源码
    在这里插入图片描述
    在这里插入图片描述
  5. doGetBean,官方注释说明,使用了原型模式
    在这里插入图片描述

2. 序列化和反序列化

具体序列化如何做,参考上面深拷贝,通过反序列化实现深拷贝,的代码
序列化是干嘛的
  1. 序列化的本意是将对象变成字节序列方便持久化到磁盘,避免程序运行结束后对象从内存消失,字节序列便于网络运输和传播
序列化和反序列化
  1. 序列化:把Java对象转换为字节序列
  2. 反序列化:把字节序列恢复为原先的Java对象
为什么实现序列化需要继承Serializable并指定序列化ID
  1. 首先Serializable接口是个空接口,如果序列化对象不加implements Serializable会报错NotSerializableException,所以我们看看为什么会报这个错
    在这里插入图片描述
  2. 异常在ObjectOutputStream类的writeObject0()方法抛出,我们发现如果它不属于字符串、数组、枚举,也没实现Serializable接口,就会抛出NotSerializableException异常!所以,Serializable接口只是做标记作用
    在这里插入图片描述
  3. 结论:实现Serializable接口的类,表示标记自己是可以被序列化的。Serializable接口是空接口,真正的序列化动作不靠它完成
serialVersionUID号
  1. 代码中经常定义如下代码变量serialVersionUID
    在这里插入图片描述
  2. serialVersionUID是序列化前后的唯一标识符
  3. 如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个,如何的结构或者信息发生变化,则类的serialVersionUID也会跟着变
  4. 反序列化时JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化否则就会报InvalidClassException异常来终止反序列化的过程
  5. 所以,凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID
transient修饰符
  1. 序列化对象时,不希望某个字段被序列化(比如密码等),可以用transient修饰符来修饰该字段
  2. 被transient修饰的字段,不会被序列化
不会被序列化的东西
  1. static修饰的字段不会被序列化(因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域)
  2. 被transient修饰符修饰的字段不会被序列化
序列化的受控和加强,B站程序羊那偷学来的,原文链接:https://r2coding.com/
  1. 约束性加持
  1. 序列化和反序列化的过程其实有漏洞,序列化到反序列化有一个中间过程被别人拿到中间字节流,就可以伪造或篡改反序列化出来的对象就有风险
  2. 我们希望在反序列化时,进行受控的对象反序列化动作,自行编写readObject()函数,用于对象的反序列化构造,从而提供约束性
假设一个Student类,有一个score字段保存成绩,我们可以控制score保证在0到100之间
private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {

    // 调用默认的反序列化函数
    objectInputStream.defaultReadObject();

    // 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!
    if( 0 > score || 100 < score ) {
        throw new IllegalArgumentException("学生分数只能在0到100之间!");
    }
}

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

单例模式的增强

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殷丿grd_志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值