天天看点

java设计模式之原型模式|浅复制和深复制的了解

目录

​​一、前言​​

​​优点及适用场景​​

​​原型模式的注意事项​​

​​浅复制和深复制​​

​​二、浅复制demo演示​​

​​三、深复制demo演示​​

​​ 四、项目地址​​

一、前言

原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。在Java中,复制对象是通过clone()实现的,先创建一个原型类,通过实现Cloneable 接口

public class Prototype implements Cloneable {  

public Object clone() throws CloneNotSupportedException {  
Prototype proto = (Prototype) super.clone();  
return proto;  
    }  
  }        

只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的,说明这个方法实现并不是使用java语言,是底层C实现阿达

至于cloneA或者cloneB名字可以任意取,是因为要你主动去调用的,所以你名字取成什么,你调用的时候就调用该名字就可以了

优点及适用场景

       使用原型模式创建对象比直接new一个对象在性能上要好的多,因为上面我也提到过,Object类的clone()是native的,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

       使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

       因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

原型模式的注意事项

使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone()来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。

说到这里,就得顺便提一下单例模式,在单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。

浅复制和深复制

另外还得知道两个特别重要的概念 : 浅复制   深复制

    浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而数组、容器对象、引用对象等都不会拷贝,指向的还是原对象所指向的地址。浅拷贝实现 Cloneable,重写clone方法

    深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。深拷贝是通过实现 Serializable 读取二进制流

二、浅复制demo演示

首先我们创建一个抽象原型类 Animal.class,实现了Cloneable接口,并且重写了clone方法

package cn.zygxsq.design.module.prototypePattern;

/**
* Created by yjl on 2021/4/30.
* 动物类
*
*/
public abstract class Animal implements Cloneable{
private String id;
public String name;

abstract void shout();

public String getId() {
return id;
    }

public void setId(String id) {
this.id = id;
    }


public String getName() {
return name;
    }

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

/**
* 浅复制
*/
public Object clone() throws CloneNotSupportedException {
Animal clone = (Animal) super.clone();
return clone;
    }
}
      

再创建两个实现类

Dog.class 和Cat.class

package cn.zygxsq.design.module.prototypePattern;

/**
* Created by yjl on 2021/4/30.
*/
public class Dog extends Animal {
public Dog(){
name = "狗狗";
    }

@Override
public void shout() {
System.out.println("我的叫声是:汪汪汪");
    }
}
      
package cn.zygxsq.design.module.prototypePattern;

/**
* Created by yjl on 2021/4/30.
*/
public class Cat extends Animal {
public Cat(){
name = "猫猫";
    }

@Override
public void shout() {
System.out.println("我的叫声是:喵喵喵");
    }
}
      

然后创建一个数据缓存类,用户存储从数据库中获取到的大对象数据或者曾经使用过的大对象数据

以后下一次想要再次对这个对象数据操作的时候,直接从缓存里获取并且clone一个

package cn.zygxsq.design.module.prototypePattern;

import com.google.common.collect.Maps;
import org.springframework.beans.factory.InitializingBean;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentMap;

/**
* Created by yjl on 2021/4/30.
* 缓存类 用于加载一些数据库的缓存的数据
*/
public class DataCache /*implements InitializingBean*/{
//正常的情况是 实现 InitializingBean ,用于web服务启动的时候加载数据
// 这里测试由于不是web服务,所以就模拟加载数据

private static ConcurrentMap<String, Animal> animalCache = Maps.newConcurrentMap();

public static Animal getAnimal(String id) throws Exception{
Animal cache = animalCache.get(id);
return (Animal) cache.clone();
    }

public static void init(){
Dog dog = new Dog();
String dogid = "111";
dog.setId(dogid);
animalCache.put(dogid,dog);

Dog dog2 = new Dog();
String dogid2 = "222";
dog2.setId(dogid2);
animalCache.put(dogid2,dog2);

Cat cat = new Cat();
String catid = "333";
cat.setId(catid);
animalCache.put(catid,cat);

    }
}
      

最后咱们开始测试

先是从数据库里加载缓存,然后要从缓存里获取数据,并且的到的是一个个clone出来的对象

package cn.zygxsq.design.module.prototypePattern;

import com.alibaba.fastjson.JSON;

/**
* Created by yjl on 2021/4/30.
* 测试主类 浅复制
*
*/
public class TestPrototype {
public static void main(String[] args) {
DataCache.init(); // 模拟加载数据到缓存中

try {
Animal animal = DataCache.getAnimal("111");
System.out.println(animal.getName()+"---"+JSON.toJSONString(animal));

Animal animal222 = DataCache.getAnimal("222");
System.out.println(animal222.getName()+"---"+JSON.toJSONString(animal222));

Animal animal333 = DataCache.getAnimal("333");
System.out.println(animal333.getName()+"---"+JSON.toJSONString(animal333));

animal.shout();
animal222.shout();
animal333.shout();
        } catch (Exception e) {
e.printStackTrace();
        }


    }
}
      

运行结果: 

java设计模式之原型模式|浅复制和深复制的了解

小伙伴们看的时候,好像没什么问题,确实没什么问题,但是细细一看,还是有一定的问题的

package cn.zygxsq.design.module.prototypePattern;

import com.alibaba.fastjson.JSON;

/**
* Created by yjl on 2021/4/30.
* 浅复制遇到的问题
*/
public class TestCloneProblem {

public static void main(String[] args) {
//做完TestPrototype的main方法后,好像觉得浅复制没有什么问题
//那么可以看一下下面的a1的name 和 克隆后的name指向的是同一个地址
DataCache.init(); // 模拟加载数据到缓存中

try {
Animal a1 = DataCache.getAnimal("111");
Animal a2 = (Animal)a1.clone();
System.out.println(a1==a2);
System.out.println(a1.name == a2.name);
        } catch (Exception e) {
e.printStackTrace();
        }
    }

}
      

运行结果:

大家踩一下 a1的name 和 克隆后的name是什么样的关系呢

java设计模式之原型模式|浅复制和深复制的了解

大家可以看到,a1的name和a2的name是一样的,由于他们的类型是String,所以他们指向的是同一个地址,名称为“狗狗”的引用地址,大概的样子可以看下图

java设计模式之原型模式|浅复制和深复制的了解

那怎么样才能不让a1.name 和a2.name不相同呢,也就是完完全全的复制,这个就得用到深复制了

深复制其实用到的就是流复制

可以在clone()的方法定义一个深复制的方法,比如deepClone()

三、深复制demo演示

记住,深复制的时候,方法一定得实现可序列化,Serializable

package cn.zygxsq.design.module.prototypePattern;

import java.io.*;

/**
* Created by yjl on 2021/4/30.
* 动物类
* 
*/
public abstract class Animal implements Cloneable, Serializable{
private String id;
public String name;

abstract void shout();

public String getId() {
return id;
    }

public void setId(String id) {
this.id = id;
    }


public String getName() {
return name;
    }

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

/**
* 浅复制
*/
public Object clone() throws CloneNotSupportedException {
Animal clone = (Animal) super.clone();
return clone;
    }


/**
* 深复制
*/
public Object deepClone() {
ByteArrayOutputStream byteArrayOutputStream = null;
ObjectOutputStream objectOutputStream = null;
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
// 序列化
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);/*将当前对象以对象流的方式输出*/
//反序列化
byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
objectInputStream = new ObjectInputStream(byteArrayInputStream);
Animal deepProtoType = (Animal) objectInputStream.readObject();
return deepProtoType;
        } catch (Exception e) {
e.printStackTrace();
return null;
        } finally {
try {
byteArrayOutputStream.close();
objectOutputStream.close();
byteArrayInputStream.close();
objectInputStream.close();
            } catch (IOException e) {
e.printStackTrace();
            }

        }

    }
}
      
java设计模式之原型模式|浅复制和深复制的了解

测试一下结果

package cn.zygxsq.design.module.prototypePattern;

import com.alibaba.fastjson.JSON;

/**
* Created by yjl on 2021/4/30.
* 测试主类 深复制
*
public class TestPrototypeDeepClone {
public static void main(String[] args) {
DataCache.init(); // 模拟加载数据到缓存中

try {
Animal a1 = DataCache.getAnimal("111");
Animal a2 = (Animal)a1.deepClone();
System.out.println(a1==a2);
System.out.println(a1.name == a2.name);
System.out.println(a1.name);
} catch (Exception e) {
e.printStackTrace();
}


}
}
      

 运行结果:

java设计模式之原型模式|浅复制和深复制的了解

这就是深复制和浅复制以及原型模式的使用

 四、项目地址

github地址:

继续阅读