1.定义
使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
面向对象语言中,拷贝分为两类: 1. 浅拷贝 对于基础数据类型(如Java中short/int/long/float/double/char/byte/boolean),直接复制数值,对于引用数据类型,则直接复制引用,修改此引用指向的对象会对原引用对象造成影响 2. 深拷贝 对于基础数据类型,直接复制数值,对于引用数据类型,则拷贝一份引用对象至分配到的新内存中,修改此对象对原引用对象不会有影响2.UML图
Client:客户端用户,提出创建克隆对象的请求
Prototype:原型接口,标识对象是否可以克隆 ConcretePrototye:具体实例,实现了原型接口的类,通过clone()方法可返回自身的一个副本3.实现
Java中的类都继承自 java.lang.Object。Object 类提供有一个 clone() 方法,可以将一个 Java 对象复制一份。因此在 Java 中可以直接使用 Object 提供的 clone() 方法来实现对象的克隆。
需要注意的是能够实现克隆的 Java 类都必须实现一个标识接口 Cloneable,表示这个 Java 类支持被复制。如果一个类没有实现这个接口但是调用了 clone() 方法,Java 编译器将抛出一个 CloneNotSupportedException 异常。 以下以持有手机的个人的克隆为示例 1. 简单形式 个人所持有的手机Phone类1 package com.creasy.testjava; 2 3 public class Phone{ 4 5 private String phone_brand; 6 private String phone_number; 7 8 public Phone() { 9 }10 11 public void setBrandAndNumber(String phone_brand, String phone_number) {12 this.phone_brand = phone_brand;13 this.phone_number = phone_number;14 }15 16 public void Display() {17 System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);18 }19 20 }
个人类Person
1 package com.creasy.testjava; 2 3 public class Person implements Cloneable { 4 5 private String name; 6 private int age; 7 private Phone phone; 8 9 public Person() {10 }11 12 @Override13 protected Object clone() throws CloneNotSupportedException {14 Person p = (Person)super.clone();15 return p;16 }17 18 public void setNameAndAge(String name, int age, Phone phone) {19 this.name = name;20 this.age = age;21 this.phone = phone;22 }23 24 public void Display() {25 System.out.print("name:" + name + " and age:" + age + " ");26 if (null == phone) {27 System.out.println("has no phone");28 } else {29 phone.Display();30 }31 System.out.println();32 }33 34 public String getName() {35 return name;36 }37 38 public int getAge() {39 return age;40 }41 42 public Phone getPhone() {43 return phone;44 }45 46 }
客户端调用:
1 package com.creasy.testjava; 2 3 /** 4 * @author laicreasy 5 * 6 */ 7 public class TestBaseJavaFunction { 8 9 public static void main(String[] args) {10 try {11 Person a = new Person();12 Phone phone = new Phone();13 phone.setBrandAndNumber("iPhone", "1234567890");14 a.setNameAndAge("xiaoli", 18, phone);15 Person b = (Person) a.clone();16 a.Display();17 phone.setBrandAndNumber("Android", "0987654321");18 a.Display();19 b.Display();20 System.out.println(a.getPhone() == b.getPhone());21 } catch (CloneNotSupportedException e) {22 e.printStackTrace();23 }24 }25 26 }
输出结果:
1 name:xiaoli and age:18 phone_brand:iPhone and phone_number:12345678902 name:xiaoli and age:18 phone_brand:Android and phone_number:09876543213 name:xiaoli and age:18 phone_brand:Android and phone_number:09876543214 true
由结果可以看出,Person中Phone的克隆只是简单的复制了引用给到新对象。如果Phone类改成
1 package com.creasy.testjava; 2 3 public class Phone implements Cloneable{ 4 5 private String phone_brand; 6 private String phone_number; 7 8 public Phone() { 9 }10 11 public void setBrandAndNumber(String phone_brand, String phone_number) {12 this.phone_brand = phone_brand;13 this.phone_number = phone_number;14 }15 16 public void Display() {17 System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);18 }19 20 @Override21 protected Object clone() throws CloneNotSupportedException {22 return super.clone();23 }24 }
而Person类中的clone方法改成
1 而Person类中的clone方法改成2 3 @Override4 protected Object clone() throws CloneNotSupportedException {5 Person p = (Person)super.clone();6 p.phone = (Phone)this.phone.clone();7 return p;8 }
再次运行,得到结果
1 name:xiaoli and age:18 phone_brand:iPhone and phone_number:12345678902 name:xiaoli and age:18 phone_brand:Android and phone_number:09876543213 name:xiaoli and age:18 phone_brand:iPhone and phone_number:12345678904 false
由结果可以看到,此时则是深层复制,a和b中的phone指向的是不同的对象的
2. 实现Serializable形式 序列化的过程就是把数据从内存中写入流中,反序列化则是从流中把数据再写入新内存中,通过序列化和反序列化则可以实现对象的深层复制 Phone类1 package com.creasy.testjava; 2 3 import java.io.Serializable; 4 5 public class Phone implements Serializable{ 6 7 private static final long serialVersionUID = 8785807031027660303L; 8 9 private String phone_brand;10 private String phone_number;11 12 public Phone() {13 }14 15 public void setBrandAndNumber(String phone_brand, String phone_number) {16 this.phone_brand = phone_brand;17 this.phone_number = phone_number;18 }19 20 public void Display() {21 System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);22 }23 24 }
Person类:
1 package com.creasy.testjava; 2 3 import java.io.ByteArrayInputStream; 4 import java.io.ByteArrayOutputStream; 5 import java.io.IOException; 6 import java.io.ObjectInputStream; 7 import java.io.ObjectOutputStream; 8 import java.io.Serializable; 9 10 public class Person implements Serializable {11 12 /**13 * 14 */15 private static final long serialVersionUID = 5767629194216311414L;16 17 private String name;18 private int age;19 private Phone phone;20 21 public Person() {22 }23 24 public Person deepClone() throws IOException, ClassNotFoundException {25 //将对象写入流中26 ByteArrayOutputStream bao=new ByteArrayOutputStream();27 ObjectOutputStream oos=new ObjectOutputStream(bao);28 oos.writeObject(this);29 //将对象从流中取出30 ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());31 ObjectInputStream ois=new ObjectInputStream(bis);32 return (Person)ois.readObject();33 }34 35 public void setNameAndAge(String name, int age, Phone phone) {36 this.name = name;37 this.age = age;38 this.phone = phone;39 }40 41 public void Display() {42 System.out.print("name:" + name + " and age:" + age + " ");43 if (null == phone) {44 System.out.println("has no phone");45 } else {46 phone.Display();47 }48 System.out.println();49 }50 51 public String getName() {52 return name;53 }54 55 public int getAge() {56 return age;57 }58 59 public Phone getPhone() {60 return phone;61 }62 63 }
客户端调用
1 package com.creasy.testjava; 2 3 /** 4 * @author laicreasy 5 * 6 */ 7 public class TestBaseJavaFunction { 8 9 public static void main(String[] args) {10 try {11 Person a = new Person();12 Phone phone = new Phone();13 phone.setBrandAndNumber("iPhone", "1234567890");14 a.setNameAndAge("xiaoli", 18, phone);15 Person b = (Person) a.deepClone();16 a.Display();17 phone.setBrandAndNumber("Android", "0987654321");18 a.Display();19 b.Display();20 System.out.println(a.getPhone() == b.getPhone());21 } catch (Exception e) {22 e.printStackTrace();23 }24 }25 26 }
运行结果
1 name:xiaoli and age:18 phone_brand:iPhone and phone_number:12345678902 name:xiaoli and age:18 phone_brand:Android and phone_number:09876543213 name:xiaoli and age:18 phone_brand:iPhone and phone_number:12345678904 false
由结果可以看出,对应a和对象b是不同的对象,并且他们各自持有不同的phone对象
通过序列化及反序列化进行深层复制的前提是对象以及对象内部所有引用到的对象都是可序列化的,否则,需要声明不可序列化的对象成transient,从而将之排除在复制过程之外。关于transient关键字的描述,可参考:4.Android或者Java中的使用
Android中,存储Integer和Object的键值对类SparseArray实现了Cloneable,直接看其clone源码,如下:
1 private int[] mKeys; 2 private Object[] mValues; 3 4 @Override 5 @SuppressWarnings("unchecked") 6 public SparseArrayclone() { 7 SparseArray clone = null; 8 try { 9 clone = (SparseArray ) super.clone();10 clone.mKeys = mKeys.clone();11 clone.mValues = mValues.clone();12 } catch (CloneNotSupportedException cnse) {13 /* ignore */14 }15 return clone;16 }
使用SparseArray测试,代码及结果如下
1 public void testSparseArray() { 2 SparseArraymSparseArray = new SparseArray (); 3 Phone phone1 = new Phone(); 4 phone1.setBrandAndNumber("SANXING", "123456789"); 5 Phone phone2 = new Phone(); 6 phone2.setBrandAndNumber("APPLE", "123456789"); 7 mSparseArray.put(0, phone1); 8 mSparseArray.put(1, phone2); 9 //mSparseArray110 for( int i=0; i < mSparseArray.size(); i++ ) {11 Phone p = mSparseArray.get(i);12 System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());13 }14 SparseArray mSparseArray2 = mSparseArray.clone();15 for( int i=0; i < mSparseArray2.size(); i++ ) {16 Phone p = mSparseArray.get(i);17 p.setBrandAndNumber(p.getPhone_brand() + "2", p.getPhone_number() + "2");18 }19 //mSparseArray220 for( int i=0; i < mSparseArray2.size(); i++ ) {21 Phone p = mSparseArray2.get(i);22 System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());23 }24 //mSparseArray125 for( int i=0; i < mSparseArray.size(); i++ ) {26 Phone p = mSparseArray.get(i);27 System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());28 }29 //mSparseArray230 for( int i=0; i < mSparseArray2.size(); i++ ) {31 Phone p = mSparseArray2.get(i);32 System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());33 }34 System.out.println(mSparseArray == mSparseArray2);35 }
结果
由上,可见SparseArray中clone方法执行的是浅层复制功能(实际上SparseArray中存储的Object为数组,而数组的clone执行的是浅层复制功能)
5.使用场景
(1) 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的 CPU 资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得
(2)当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候,不适合使用原型模式参考博客: