原型模式 深拷贝/浅拷贝

原型模式 深拷贝/浅拷贝

原型模式 :即通过clone模式将原有对象复制成一个新对象,来代替使用new的方式创建对象
好处就是,当创建一个大的对象时,使用new的方式内存开销很大,因此可以采用clone方式直接复制一个对象
下面通过代码来测试一下:
代码属于伪代码,缺少get,set即toString方法

//Object类实际上是有clone方法的,但是它是被声明成被保护的,用protected修饰的,因此需要实现Cloneable接口,
//实现Cloneable,此接口没有实现方法,只起到标记的作用,然后在类中重写Object中的clone方法,调用父类的clone方法
public class Phone implements Cloneable{
    private String type;
    private double screen;
    private Photo photo;
    public Phone clonePhone(){
        Phone clonePhone=null;
        try {
            clonePhone= (Phone) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clonePhone;
    }
    public Phone(String type,double screen,Photo photo){
        this.type=type;
        this.screen =screen;
        this.photo=photo;
        try {
        //此处模拟生成对象消耗的时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Photo类,此时没有用可以暂时不去关注,后续会用来区分深拷贝和浅拷贝

public class Photo {
    private long pixel;
    private double size;

    public Photo(long pixel, double size) {
        this.pixel = pixel;
        this.size = size;
    }
    }

然后编写测试方法,查看其运行效率

 @org.junit.Test
    public void test1(){
        int maxCount=10;
        int i=0;
        //使用new来创建十个Phone对象耗时
        long startTime=System.currentTimeMillis();
        while(i<maxCount){
            Phone phone = new Phone("Aphone",5.5,new Photo(1000,10.5));
            phone.sendSms("发送短信",String.valueOf(i));
            i++;
        }
        long endTime=System.currentTimeMillis();
        System.out.println("new方式创建对象耗时:"+(endTime - startTime)+"ms");
        //使用clone模式来创建十个Phone对象耗时
        long startCloneTime=System.currentTimeMillis();
        Phone phone = new Phone("Aphone",5.5,new Photo(1000,10.5));
        while(i<maxCount){
            Phone  clonePhone = phone.clone();
            clonePhone.setScreen(0);
            clonePhone.getPhoto().setSize(0);
            clonePhone.sendSms("发送短信",String.valueOf(i));
            i++;
        }
        long endCloneTime=System.currentTimeMillis();
        System.out.println("clone方式创建对象耗时:"+(endCloneTime - startCloneTime)+"ms");
    }

显然new对象要比clone方式消耗时间长很多,因此在需要创建多个同一的大对象时,尽量使用clone方式生成 是在这里插入图片描述
上述方式只是一个浅拷贝的方式,它会将所有的属性拷贝一份放入新对象中。如果是基本数据类型,新对象属性的变化不会影响到原对象属性值;但是当它的属性时引用类型时,浅拷贝会将引用的实际内存地址,即对象在堆上分配的内存地址,复制一份给新对象的实例变量,并没有在堆上重新开辟一块区域。这时,实际上新对象和原对象其实指向的是同一个内存地址,其中一个改变对象的属性,就会影响到另一个对象。下面可以验证一下
首先,Phone类和Photo类不变,创建一个测试方法来观察他们的属性变化

@org.junit.Test
    public void test2() {
        int i = 0;
        Phone phone = new Phone("Aphone", 5.5, new Photo(1000, 10.5));
        System.out.println("原手机对象:" + phone);
        Phone clonePhone = phone.clone();
        //给克隆对象基本类型设置参数
        clonePhone.setScreen(0);
        //获取克隆对象的成员属性Photo对象,并重新给其赋值
        clonePhone.getPhoto().setSize(0);
        System.out.println("克隆对象:" +clonePhone);
        clonePhone.sendSms("发送短信", String.valueOf(i));
        System.out.println("克隆对象重新对变量进行赋值后,原手机对象:"+phone);
    }

运行结果
测试
运行结果中,克隆对象的基本数据类型screen修改为0.0,并没有影响原对象该属性。而对其引用变量的属性重新赋值时导致,原对象此引用变量的属性值发生了更改。因此浅拷贝会破坏原有对象的引用变量,会造成一定的混乱,因此一般都会采用深拷贝的方式赋值对象,比如在mybatis框架源码中,在将标签解析并放入MappedStatement对象时,在分析sql过程中,如果发现sql中有标签时,会从上一步生成的此标签节点对象深拷贝一份,用来做后续处理,避免污染原有对象。如下图所示,
在这里插入图片描述
在这里插入图片描述
再贴一张上面描述的和标签,以免过于抽象
在这里插入图片描述深拷贝: 简单地说,深拷贝就是对引用数据类型的成员变量的对象开辟了新的内存空间,当引用的对象里还有引用时也会继续向下递归开辟新的内存空间。
实现方式:
第一种,既然浅拷贝可以正常复制基本数据类型,因此可以用递归的方式,对成员变量是引用的对象再实现CloneAble接口,对其属性也进行克隆;上代码:
首先对Photo类进行改造,让其实现Cloneable接口,并重写clone方法

public class Photo implements Cloneable{
    private long pixel;
    private double size;

    public Photo(long pixel, double size) {
        this.pixel = pixel;
        this.size = size;
    }
    public Photo clone(){
        Photo clonePhoto=null;
        try {
            clonePhoto= (Photo) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clonePhoto;
    }
    }

然后,修改Phone的clone方法,通过调用photo对象的clone方法,重新为photo属性赋值

public class Phone implements Cloneable{
    private String type;
    private double screen;
    private Photo photo;
    public Phone clone(){
        Phone clonePhone=null;
        try {
            clonePhone= (Phone) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        clonePhone.setPhoto(this.photo.clone());
        return clonePhone;
    }
    }

最后,测试一下

 @org.junit.Test
    public void test3() {
        Phone phone = new Phone("Aphone", 5.5, new Photo(1000, 10.5));
        System.out.println("原手机对象:" + phone);
        Phone clonePhone = phone.clone();
        //给克隆对象基本类型设置参数
        clonePhone.setScreen(0);
        //获取克隆对象的成员属性Photo对象,并重新给其赋值
        clonePhone.getPhoto().setSize(0);
        System.out.println("克隆对象:" +clonePhone);
        System.out.println("克隆对象重新对变量进行赋值后,原手机对象:"+phone);
    }

运行结果:
在这里插入图片描述
结果显示,克隆对象中引用变量的变化并未影响到原对象的值。
但是,这种方式有一种弊端就是,我所有的对象都要去实现Cloneable并重写Object的clone()方法,这样就回导致代码复杂度提高,当对象过多时或这引用太深时,代码会越来越混乱,不易维护。但是此方法比较容易实现对象的clone
第二种方式:
使用流来对对象进行复制,这也是用的比较多的clone方式。

 @org.junit.Test
    public void test4(){
        Phone phone = new Phone("Aphone", 5.5, new Photo(1000, 10.5));
        System.out.println("原对象:"+phone);
        ByteOutputStream bos= new ByteOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(phone);
            oos.flush();
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            Phone clonePhone= (Phone) ois.readObject();
            ois.close();
            clonePhone.setScreen(0);
            clonePhone.getPhoto().setSize(0);
            System.out.println("克隆对象属性变化后,原对象:"+phone);
            System.out.println("克隆对象:"+clonePhone);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这种方式下,需要被克隆的类无需实现Cloneable接口以及重写clone方法,大大减少的了重复代码,不需要对每个类都进行特殊处理,只需在需要克隆对象的地方自行序列化/反序列化克隆即可。

//伪代码,要想使用流处理对对象进行序列化和反序列化,必须要实现Serializable 标志此类可以被序列化,如果没有实现此接口,将会抛出异常
public class Phone implements Serializable {
    private static final long serialVersionUID = 8030191510212419448L;
    private String type;
    private double screen;
    private Photo photo;
    }
 public class Photo implements Serializable {
    private static final long serialVersionUID = -5894538960278714681L;
    private long pixel;
    private double size;
    }

在这里插入图片描述
运行结果正常,与第一种克隆结果一致
在这里插入图片描述
关于基本数据类型为什么可以直接复制,并重新赋值,网上说的是因为基本数据类型采用的是值传递的形式,但是我觉得应该跟基本数据类型在jvm中存储区域有关,目前还在学习中,请多多指教!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1) 优秀的程序应该是这样的:阅读时,感觉很优雅;新增功能时,感觉很轻松;运行时,感觉很快速,这就需要设计模式支撑。2) 设计模式包含了大量的编程思想,讲授和真正掌握并不容易,网上的设计模式课程不少,大多讲解的比较晦涩,没有真实的应用场景和框架源码支撑,学习后,只知其形,不知其神。就会造成这样结果: 知道各种设计模式,但是不知道怎么使用到真实项目。本课程针对上述问题,有针对性的进行了升级 (1) 授课方式采用 图解+框架源码分析的方式,让课程生动有趣好理解 (2) 系统全面的讲解了设计模式,包括 设计模式七大原则、UML类图-类的六大关系、23种设计模式及其分类,比如 单例模式的8种实现方式、工厂模式的3种实现方式、适配器模式的3种实现、代理模式的3种方式、深拷贝等3) 如果你想写出规范、漂亮的程序,就花时间来学习下设计模式吧课程内容和目标本课程是使用Java来讲解设计模式,考虑到设计模式比较抽象,授课采用 图解+框架源码分析的方式1) 内容包括: 设计模式七大原则(单一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML类图(类的依赖、泛化和实现、类的关联、聚合和组合) 23种设计模式包括:创建型模式:单例模式(8种实现)、抽象工厂模式、原型模式、建造者模式、工厂模式。结构型模式:适配器模式(3种实现)、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式(3种实现)。行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)2) 学习目标:通过学习,学员能掌握主流设计模式,规范编程风格,提高优化程序结构和效率的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值