什么是原型模式
- 原型模式是从一个已存在对象直接复制出一个相互独立但是属性完全一样对象
- 复制的两种手段,克隆和序列化,并且都不会触发构造方法的执行,克隆区分浅拷贝和深拷贝
原型模式的使用场景
- 多个地方基于相同对象做各自独立的修改
- 希望逃脱构造器的约束
原型模式的优缺点
- 优点
- 在内存中直接二进制流进行拷贝,不用
- 可以避免构造器的约束
- 缺点
- 也是因为没有构造器的约束,会逃脱构造器可能设置的约束条件,比如单例模式中在构造器中对已有实例的判断,如果使用clone或序列化会破坏单例模式。单例模式下要避免这个问题,1.改动对象不要实现Cloneable接口 2.序列化可以参考单例模式
案例
public class Mail implements Cloneable, Serializable {
private String name;
private String context;
private String title;
private String address;
private List<Attachment> attachmentList;
public Mail(String name, String context, String title, String address) {
super();
this.name = name;
this.context = context;
this.title = title;
this.address = address;
System.out.println("constructor");
}
public Mail() {
System.out.println("defualt constructor");
}
// get/set 省略...
@Override
protected Mail clone() throws CloneNotSupportedException {
Mail mail = null;
mail = (Mail) super.clone();
if (!CollectionUtils.isEmpty(mail.getAttachmentList())) {
List<Attachment> list = new ArrayList<Attachment>();
for (Attachment attachment : mail.getAttachmentList()) {
list.add(attachment.clone());
}
mail.setAttachmentList(list);
}
return mail;
}
// 序列化进行深拷贝
public Mail deepCopy() {
Mail mail = null;
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
mail = (Mail) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return mail;
}
@Override
public String toString() {
return "Mail [name=" + name + ", context=" + context + ", title=" + title + ", address=" + address + ", attachmentList=" + attachmentList
+ "]";
}
}
// 引用类
public class Attachment implements Cloneable, Serializable {
private String title;
private Integer size;
public Attachment() {
}
public Attachment(String title, Integer size) {
this.title = title;
this.size = size;
}
// get set 省略...
@Override
public String toString() {
return "Attachment [title=" + title + ", size=" + size + "]";
}
@Override
protected Attachment clone() throws CloneNotSupportedException {
return (Attachment) super.clone();
}
}
//测试类
public class MailTest {
public static void main(String args[]) throws CloneNotSupportedException {
Mail mail = new Mail("kk", "test context", "test title", "test@qq.com");
mail.setAttachmentList(Arrays.asList(new Attachment("file1", 60), new Attachment("file2", 90)));
Mail mail1 = mail.clone();
System.out.println(mail.hashCode() + " : " + mail + "\n->" + mail.getAttachmentList().hashCode() + " : " + mail.getAttachmentList());
printAttachment(mail.getAttachmentList());
System.out.println(mail1.hashCode() + " : " + mail1 + "\n->" + mail1.getAttachmentList().hashCode() + " : " + mail1.getAttachmentList());
printAttachment(mail1.getAttachmentList());
Mail mail2 = mail.deepCopy();
System.out.println(mail2.hashCode() + " : " + mail2 + "\n->" + mail2.getAttachmentList().hashCode() + " : " + mail2.getAttachmentList());
printAttachment(mail2.getAttachmentList());
}
public static void printAttachment(List<Attachment> list) {
for (Attachment attachment : list) {
System.out.println(attachment.hashCode() + " : " + attachment);
}
}
}
打印结果:
constructor
356573597 : Mail [name=kk, context=test context, title=test title, address=test@qq.com, attachmentList=[Attachment [title=file1, size=60], Attachment [title=file2, size=90]]]
->-2009286544 : [Attachment [title=file1, size=60], Attachment [title=file2, size=90]]
1735600054 : Attachment [title=file1, size=60]
21685669 : Attachment [title=file2, size=90]
2133927002 : Mail [name=kk, context=test context, title=test title, address=test@qq.com, attachmentList=[Attachment [title=file1, size=60], Attachment [title=file2, size=90]]]
->1407063357 : [Attachment [title=file1, size=60], Attachment [title=file2, size=90]]
1836019240 : Attachment [title=file1, size=60]
325040804 : Attachment [title=file2, size=90]
1028214719 : Mail [name=kk, context=test context, title=test title, address=test@qq.com, attachmentList=[Attachment [title=file1, size=60], Attachment [title=file2, size=90]]]
->-1629438326 : [Attachment [title=file1, size=60], Attachment [title=file2, size=90]]
500977346 : Attachment [title=file1, size=60]
20132171 : Attachment [title=file2, size=90]
从打印结果可以看到案例中可以得到下面几点:
- 只有在
new Mail(...)
会执行对应的构造器方法,序列化和clone都没有执行任何构造方法就创建了新对象 - 无论是序列化还是clone产生的新对象的hashcode都不相同,即在内存中是完全独立的两个对象
spring bean的prototype
spring bean配置scope=prototype即原型模式,但是生产的bean并不是通过序列化或clone而来,而是通过bean.xml配置的构造器(默认是无参构造器)创建一个新对象
clone和final
- 如何理解“将类设为final可禁止克隆的发生”
- 这句话有一个前提:该类没有实现Cloneable标记接口,如果final修饰的类本身实现了Cloneable接口且重写了Object.clone()方法,这样的类仍然可以操作克隆
- 满足上面条件的final类可以禁止克隆的原因在于final类不能被继承,他本身不能被克隆,并且也不能通过子类实现Cloneable去克隆
- 正常类被final修饰的属性“不能”被深拷贝
final修饰的属性值不能变,如果被final修饰的属性在类初始化完成后,值就不能改变了,所以在clone方法中对引用对象赋值会编译失败。但是这里“不能”是带引号的,如果引用对象本身的属性可以被修改且允许被克隆,可以对对象属性进行克隆后覆盖赋值给final属性对象也可以达到深拷贝克隆