原型模式基本介绍
原型模式-原理结构图
原理结构图说明
1、Prototype:原型类,声明一个克隆自己的接口。
2、ConcretePrototype:具体的原型类,实现一个克隆自己的操作。
3、client:让一个原型对象克隆自己,从而创建一个新的对象(属性一样)
原型模式-代码实现
1、创建实体类,实现cloneable接口,重写clone方法。
package prototype;
/**
* @Date 2020/5/23 下午4:19
* @Created by zhaoli
*/
public class Sheep implements Cloneable{
private String name;
private Integer age;
private String color;
public Sheep(String name, Integer age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public Sheep() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
@Override
protected Object clone(){
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
System.out.println(sheep);
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
2、客户端
package prototype;
/**
* @Date 2020/5/23 下午4:23
* @Created by zhaoli
*/
public class Client {
public static void main(String[] args) {
System.out.println("==========使用原型模式对对象进行克隆==========");
Sheep sheep = new Sheep("doli",1,"white");
Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
Sheep sheep4 = (Sheep) sheep.clone();
System.out.println("sheep:"+sheep.hashCode());
System.out.println("sheep1:"+sheep1.hashCode()) ;
System.out.println("sheep2:"+sheep2.hashCode());
}
}
3、运行结果
深入讨论-深拷贝 浅拷贝
浅拷贝介绍
1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2)对于数据类型是引用类型的成员变量,比如成员变量是某个数组,某个类的对象等,那么浅拷贝会进行引用传递,也就是将该成员变量的引用值(内存地址)复制一份给新的对象。实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象该成员变量的值。
3)浅拷贝是使用默认的clone()方法来实现。
sleep = (Sleep)super.clone();
package prototype;
/**
* @Date 2020/5/23 下午4:19
* @Created by zhaoli
*/
public class Sheep implements Cloneable{
private String name;
private Integer age;
private String color;
//引用类型
private Address address;
public Sheep(String name, Integer age, String color, Address address) {
this.name = name;
this.age = age;
this.color = color;
this.address = address;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Sheep() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", address=" + address +
'}';
}
@Override
protected Object clone(){
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
class Address{
private String address;
public Address(String address) {
this.address = address;
}
public Address() {
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
package prototype;
/**
* @Date 2020/5/23 下午4:23
* @Created by zhaoli
*/
public class Client {
public static void main(String[] args) {
System.out.println("==========使用原型模式对对象进行克隆==========");
Sheep sheep = new Sheep("doli",1,"white",new Address("晋中"));
Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
Sheep sheep4 = (Sheep) sheep.clone();
System.out.println("sheep:"+sheep.hashCode());
System.out.println("sheep1:"+sheep1.hashCode()) ;
System.out.println("sheep2:"+sheep2.hashCode());
System.out.println(sheep1.getName().hashCode());
System.out.println(sheep2.getName().hashCode());
System.out.println("sheep:"+sheep.getAddress().hashCode());
System.out.println("sheep1:"+sheep1.getAddress().hashCode());
System.out.println("sheep2:"+sheep2.getAddress().hashCode());
}
}
运行结果
分析结果可以得出
sheep1和sheep2是不同的对象,但是三个sheep里面的address指向的是同一个内存地址,因为hashcode值相同。
深拷贝
创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。
那么如何实现深拷贝?
深拷贝的原理我们知道了,就是要让原始对象和克隆之后的对象所具有的引用类型属性不是指向同一块堆内存,这里有三种实现思路。
①、让每个引用类型属性内部都重写clone() 方法
既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Sheep 类有一个引用类型 Address(其实String 也是引用类型,但是String类型有点特殊,后面会详细讲解),我们在 Address 类内部也重写 clone 方法。如下:
package prototype;
/**
* @Date 2020/5/23 下午4:19
* @Created by zhaoli
*/
public class Sheep implements Cloneable{
private String name;
private Integer age;
private String color;
//引用类型
private Address address;
public Sheep(String name, Integer age, String color, Address address) {
this.name = name;
this.age = age;
this.color = color;
this.address = address;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Sheep() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", address=" + address +
'}';
}
@Override
protected Object clone(){
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
sheep.address = (Address) address.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return sheep;
}
}
class Address implements Cloneable{
private String address;
public Address(String address) {
this.address = address;
}
public Address() {
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
再次运行主类
可以看到这时候address内存地址是不同的,这时候我们就实现了深拷贝。但是这种方式存在一个问题,如果address还有一个引用对象,那么要实现深拷贝,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
②、利用序列化
序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。
注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。
package prototype;
import java.io.*;
/**
* @Date 2020/5/23 下午4:23
* @Created by zhaoli
*/
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fos = new FileOutputStream("t.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(new Sheep("doli",1,"white",new Address("晋中")));
new Thread(()->{
FileInputStream fis = null;
try {
fis = new FileInputStream("t.tmp");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(fis);
} catch (IOException e) {
e.printStackTrace();
}
Sheep sheep = null;
try {
sheep = (Sheep) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//Sheep sheep2 = (Sheep) ois.readObject();
System.out.println("sheep:"+sheep) ;
System.out.println("sheep.hashcode:"+sheep.hashCode());
System.out.println("sheep address:"+sheep.getAddress().hashCode());
}).start();
new Thread(()->{
FileInputStream fis = null;
try {
fis = new FileInputStream("t.tmp");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(fis);
} catch (IOException e) {
e.printStackTrace();
}
Sheep sheep = null;
try {
sheep = (Sheep) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("sheep1:"+sheep) ;
System.out.println("sheep1.hashcode:"+sheep.hashCode());
System.out.println("sheep1 address:"+sheep.getAddress().hashCode());
}).start();
}
}
运行结果
可以看到两个线程跑出来的对象是不同的。