「这是我参与2022首次更文挑战的第33天,活动详情查看:2022首次更文挑战」
原型模式
在java中我们知道通过new关键字创建的对象是非常繁琐的(类加载判断,内存分配,初始化等),在我们需要大量对象的情况下,原型模式就是我们可以考虑实现的方式。 原型模式我们也称为克隆模式,即一个某个对象为原型克隆出来一个一模一样的对象,该对象的属性和原型对象一模一样。而且对于原型对象没有任何影响。原型模式的克隆方式有两种:浅克隆和深度克隆
| 原型模式 | 说明 |
|---|---|
| 浅克隆 | 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝, 还是指向原生对象的内部元素地址 |
| 深度克隆 | 深复制把要复制的对象所引用的对象都复制了一遍 |
浅拷贝
首先创建一个User类,其属性有基本数据类型,也有引用数据类型。如果需要克隆,该类需要继承Cloneable接口,并重写clone方法。
原型类
package com.jony.designpattern.prototype;
import java.util.Date;
public class User implements Cloneable {
//基本数据类型
private int age;
//引用数据类型
private String name;
//引用数据类型
private School school;
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected User clone() throws CloneNotSupportedException {
return (User)super.clone();
}
@Override
public String toString() {
return super.hashCode()+"Use r{" +
"name='" + name + ''' +
", age=" + age +
", school=" + school +
'}';
}
}
class School{
private String schoolName;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
@Override
public String toString() {
return "School{" +
"schoolName='" + schoolName + ''' +
'}';
}
}
浅拷贝测试方法
package com.jony.designpattern.prototype;
public class UserTest {
public static void main(String[] args) throws CloneNotSupportedException {
School school=new School();
school.setSchoolName("希望小学");
User user=new User();
user.setName("张三");
user.setAge(10);
user.setSchool(school);
User user1=user.clone();
System.out.println(user);
System.out.println(user1);
school.setSchoolName("腾达小学");
user.setName("李四");
user.setAge(20);
System.out.println("----------------");
System.out.println(user);
System.out.println(user1);
}
}
输出结果如下:
总结
分析结果可以验证:
基本数据类型是值传递,所以修改值后不会影响另一个对象的该属性值;
引用数据类型是地址传递(引用传递),所以修改值后另一个对象的该属性值会同步被修改。
String类型非常特殊,所以我额外设置了一个字符串类型的成员变量来进行说明。首先,String类型属于引用数据类型,不属于基本数据类型,但是String类型的数据是存放在常量池中的,也就是无法修改的!也就是说,当我将name属性从“张三”改为“李四"后,并不是修改了这个数据的值,而是把这个数据的引用从指向”张三“这个常量改为了指向”李四“这个常量。在这种情况下,另一个对象的name属性值仍然指向”张三“不会受到影响。
深拷贝
从上面的结果我们得知,User通过重写了clone,其基本数据类型因为是值传递,所以完成“深拷贝”,但是User中的属性School是一个对象,在user.clone的时候,无法对Scool也进行拷贝,因此,我们要把School也进行深度拷贝,代码修改如下:
原型类
package com.jony.designpattern.prototype;
import java.util.Date;
public class User implements Cloneable {
//基本数据类型
private int age;
//引用数据类型
private String name;
//引用数据类型
private School school;
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected User clone() throws CloneNotSupportedException {
User user=(User)super.clone();
user.school=user.getSchool().clone();
return user;
}
@Override
public String toString() {
return super.hashCode()+"Use r{" +
"name='" + name + ''' +
", age=" + age +
", school=" + school +
'}';
}
}
class School implements Cloneable{
private String schoolName;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
@Override
protected School clone() throws CloneNotSupportedException {
return (School)super.clone();
}
@Override
public String toString() {
return "School{" +
"schoolName='" + schoolName + ''' +
'}';
}
}
我们在User对象中的clone方法中,把其引用对象也进行同步克隆,这样就实现了深度克隆(如果有N个引用对象,就全部需要进行clone)
测试类
package com.jony.designpattern.prototype;
public class UserTest {
public static void main(String[] args) throws CloneNotSupportedException {
School school=new School();
school.setSchoolName("希望小学");
User user=new User();
user.setName("张三");
user.setAge(10);
user.setSchool(school);
User user1=user.clone();
System.out.println(user);
System.out.println(user1);
school.setSchoolName("腾达小学");
user.setName("李四");
user.setAge(20);
System.out.println("----------------");
System.out.println(user);
System.out.println(user1);
}
}
执行结果
通过以上结果,可以得知,User中引用的School,在值进行改变之后,克隆的对象未改变,这样我们就实现了深拷贝。
总结
虽然可以通过对引用对象进行同步拷贝来实现深度拷贝,但是如果有N个引用对象,就会无形中造成较多的编码量。因此我们可以将对象进行序列操作进行深拷贝。
序列化深拷贝
原型代码
去掉实现Cloneable,改为实现Serializable接口,同时去掉User和School中的clone方法
package com.jony.designpattern.prototype;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private static final long serialVersionUID = -2546622010956625341L;
//基本数据类型
private int age;
//引用数据类型
private String name;
//引用数据类型
private School school;
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// @Override
// protected User clone() throws CloneNotSupportedException {
// User user=(User)super.clone();
// user.school=user.getSchool().clone();
// return user;
// }
@Override
public String toString() {
return super.hashCode()+"Use r{" +
"name='" + name + ''' +
", age=" + age +
", school=" + school +
'}';
}
}
class School implements Serializable {
private static final long serialVersionUID = 2281697513295893290L;
private String schoolName;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
// @Override
// protected School clone() throws CloneNotSupportedException {
// return (School)super.clone();
// }
@Override
public String toString() {
return "School{" +
"schoolName='" + schoolName + ''' +
'}';
}
}
测试类
package com.jony.designpattern.prototype;
import java.io.*;
public class UserTest {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
School school=new School();
school.setSchoolName("希望小学");
User user=new User();
user.setName("张三");
user.setAge(10);
user.setSchool(school);
//通过序列进行深拷贝
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(user);
oos.flush();
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
User user1=(User)ois.readObject();
//通过Cloneable,并对引用类型进行clone实现深拷贝
// User user1=user.clone();
System.out.println(user);
System.out.println(user1);
school.setSchoolName("腾达小学");
user.setName("李四");
user.setAge(20);
System.out.println("----------------");
System.out.println(user);
System.out.println(user1);
}
}
执行结果
总结
优点:
可以通过很简洁的代码即可完美实现深拷贝。不过要注意的是,如果某个属性被transient(添加这个关键字,属性就不会被序列化了)修饰,那么该属性就无法被拷贝了。
缺点:
序列化相对来说在性能方面还是有些影响的,序列化操作属于CPU密集型,需要解析流,这种速度相对较慢,如果应用没有太复杂,或者对性能要求不是太高,可以考虑这种方案
以上是浅拷贝的深拷贝的区别和实现方式。