原型模式是一个创建型的模式。原型二字表明了改模式应该有一个样板实例,用户从这个样板对象中复制一个内部属性一致的对象,这个过程也就是我们称的“克隆”。被复制的实例就是我们所称的“原型”,这个原型是可定制的。原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。关键就是两个字:克隆
对象克隆,说白了,就是将已实例化的对象复制一个出来,有个别同学就要问了,字节new 出来或者用反射创建一个实例出来不就好了吗? 干嘛要克隆? 当然你可以new也可以反射,但是你new出来的对象都是空的,我们克隆是是将已有内容的对象复制一个一模一样的出来,比如我们看看下面这个复制对象的案例;
// 实例化对象 StudentInfo studentInfo = new StudentInfo(); studentInfo.setName("张三"); studentInfo.setAge(16); studentInfo.setHeight(149); studentInfo.setLoginName(zhangsan); studentInfo.setStatus(1); studentInfo.setAddress("广东省深圳市南山区深圳湾一号3栋28楼2802房"); ....... studentInfo.setN("N"); // 第N个对象 // 复制对象 StudentInfo copyInfo = new StudentInfo(); copyInfo.setName(studentInfo.getName()); copyInfo.setAge(studentInfo.getAge()); copyInfo.setHeight(studentInfo.getHeight()); copyInfo.setLoginName(studentInfo.getLoginName()); copyInfo.setStatus(studentInfo.getStatus()); copyInfo.setAddress(studentInfo.getAddress()); ........ copyInfo.setN(studentInfo.getN()); // 设置第N个对象
以上的复制对象方式是可行的,但是需要每个都重新赋值一遍,太麻烦啦,而且如果对象太多的话,你不得写好多代码,维护起来也麻烦得多,
Spring默认是单例模式的,但如果在类上面加上 @Scope("prototype") 注解,就表明这个对象是原型模式,它的低层其实就是拷贝了一个容器中的对象实现的;
@Service @Scope("prototype") public class StudentService { }
让我们看看java API里面一个用到了克隆对象的方法 java.util.Date:
/** * Return a copy of this object. */ public Object clone() { Date d = null; try { d = (Date)super.clone(); if (cdate != null) { d.cdate = (BaseCalendar.Date) cdate.clone(); } } catch (CloneNotSupportedException e) {} // Won't happen return d; }
爱学习的童鞋发问:“介绍完了对象拷贝,那如果需要实现对象拷贝的话,有哪些方式呢?” ,首先,这是个好问题, 让我们把掌声送给这位童鞋,这可问到点子上了,对象拷贝啊一共有四种方式,分别为
接下来我们一个个地介绍它们的作用
有个聪明的同学马上就想出来了一个法子,并且站起来大声说:“脑师,我想到了,是不是这样的啊,(接着在电脑面前对着键盘一顿输出),先是画出一个流程图,然后敲了一段代码,这也太简单了吧, 我很聪明吧?嘿嘿”; 先看看这位童鞋画的图吧!
让我们在看看这位聪明的同学写的代码吧!
public static void main(String[] args) { Student zhangsan = new Student(new StudentInfo("张三", 15)); System.out.println("引用拷贝前张三的地址:"+zhangsan); Student copy = zhangsan; // 拷贝对象 System.out.println("引用拷贝后张三的地址:"+copy); // 将名称改为李四 System.out.println("将名称改为李四"); zhangsan.getInfo().setName("李四"); System.out.println("获取名称:"+copy.getInfo().getName()); }
可是运行之后,结果是这样的:
引用拷贝前张三的地址:com.clone.Student@66d3c617 引用拷贝后张三的地址:com.clone.Student@66d3c617 将名称改为李四 获取名称:李四
这样也没错,算是实现了拷贝对象,但注意看后面的,我把名称改为李四, 复制的对象也跟着改了;原来啊,虽然用了2个对象,但是呢,我们可以看到打印的toString() 的内存地址是一样的,所以本质上,虽然看上去复制了一个对象出来,其实本质上它们共用了一个引用,引向同一块内存地址;一个改了,另一个也会跟着改;
当然,我们还有另一种方式可以实现克隆后的对象会使用不同的地址,就是用 clone 的方式,Object 是所有类的父类,这个类有个方法叫做clone() 方法, 这个方法就是用来克隆用的,使用这个方法后,会为我们拷贝一份引用,但是也 只是拷贝引用,这2个引用也是指向同一块内存地址的;
注意:使用clone() 方法必须先实现 Cloneable 接口,否则会报错;
我们看看代码是怎么实现的吧
package com.clone; /** * 浅拷贝 */ public class StudentInfo implements Cloneable { public static void main(String[] args) throws CloneNotSupportedException { StudentInfo zhangsan = new StudentInfo("张三", 15); System.out.println("浅拷贝前张三的地址:"+zhangsan); Object clone = zhangsan.clone(); System.out.println("浅拷贝后张三的地址:"+clone); // 改为李四 System.out.println("将张三的名称改为李四"); zhangsan.setName("李四"); com.clone.StudentInfo info = (com.clone.StudentInfo) clone; System.out.println(info.getName()); } private String name; // 名称 private Integer age; // 年龄 public StudentInfo( String name,Integer age){ this.name = name;this.age = age; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
打印结果
浅拷贝前张三的地址:com.clone.StudentInfo@66d3c617 浅拷贝后张三的地址:com.clone.StudentInfo@63947c6b 将张三的名称改为李四 张三
这下我们就可以看到,这里2个对象打印的内存地址已经不一样了,并且将张三改为李四,克隆对象也不受影响;
在浅克隆中,对象的成员变量是基本数据类型(Integer 、Float、Char、Long 、Double、Short、Boolean、Byte、包括String ),但如果对象的成员变量也是一个对象,就需要把这个成员变量对象也克隆一份出来,如果不克隆对象类型的成员变量,那么它们本质上的数据还是指向同一块地址,所以我们既要克隆对象,也要克隆这个对象的成员变量;
实现代码
package com.clone; /** * 深拷贝 */ public class DeepCopy { public static void main(String[] args) throws CloneNotSupportedException { Student zhangsan = new Student(new StudentInfo("张三", 15)); System.out.println("深拷贝前张三的地址:"+zhangsan); Object clone = zhangsan.clone(); System.out.println("深拷贝后张三的地址:"+clone); // 将名称改为李四 System.out.println("将名称改为李四"); zhangsan.getInfo().setName("李四"); Student student = (Student) clone; System.out.println("获取名称:"+student.getInfo().getName()); } } class Student implements Cloneable { private StudentInfo info; public Student(StudentInfo info){ this.info = info; } public StudentInfo getInfo() { return info; } public void setInfo(StudentInfo info) { this.info = info; } @Override protected Object clone() throws CloneNotSupportedException { Object clone = super.clone(); // 浅拷贝 Student student = (Student) clone; student.info = (StudentInfo) info.clone(); // 深拷贝,拷贝对象的成员变量 return student; } } class StudentInfo implements Cloneable { private String name; // 名称 private Integer age; // 年龄 public StudentInfo( String name,Integer age){ this.name = name;this.age = age; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
打印结果
深拷贝前张三的地址:com.clone.Student@66d3c617 深拷贝后张三的地址:com.clone.Student@63947c6b 将名称改为李四 获取名称:张三
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,如果每个对象都去实现Cloneable 接口并重写clone方法的话,就会很麻烦。就像这种格式的对象:
这时我们可以用序列化的方式来实现对象的深克隆;序列化我们在学习javase基础的时候就已经学过了,用起来也是非常简单,只需要实现 Serializable 接口即可,通过 下面的代码例子可以看到,使用了更少的代码,并且不需要重写clone方法也可以实现对象深拷贝;
package com.clone; import java.io.*; /** * 克隆对象--深拷贝--序列化拷贝 */ public class SerialCloneObject { public static void main(String[] args) throws Exception { Student zhangsan = new Student(new StudentInfo("张三", 15)); System.out.println("序列化前张三的地址:"+zhangsan); // 序列化 ByteArrayOutputStream byteArrayOutputStream = serialObject(zhangsan); // 反序列化 Object o = deserialObject(byteArrayOutputStream); // 打印结果 System.out.println("序列化后张三的地址:"+o); //修改名称不会影响到深拷贝的对象 System.out.println("将名称修改为李四"); zhangsan.getInfo().setName("李四"); Student student = (Student) o; System.out.println("获取名称:"+student.getInfo().getName()); } // 序列化 private static ByteArrayOutputStream serialObject(Object object) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream); out.writeObject(object); // 关闭流 byteArrayOutputStream.close(); out.close(); return byteArrayOutputStream; } // 反序列化 private static Object deserialObject(ByteArrayOutputStream byteArrayOutputStream) throws IOException, ClassNotFoundException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream in = new ObjectInputStream(byteArrayInputStream); Object o = in.readObject(); // 关闭流 byteArrayInputStream.close(); in.close(); return o; } } class Student implements Serializable { // 实现Serializable 接口 private StudentInfo info; public Student(StudentInfo info){ this.info = info; } public StudentInfo getInfo() { return info; } public void setInfo(StudentInfo info) { this.info = info; } } class StudentInfo implements Serializable { // 实现Serializable 接口 private String name; // 名称 private Integer age; // 年龄 public StudentInfo( String name,Integer age){ this.name = name;this.age = age; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
打印结果
序列化前张三的地址:com.clone.Student@66d3c617 序列化后张三的地址:com.clone.Student@7cd84586 将名称修改为李四 获取名称:张三