原型模式
原型模式,用起来其实就是做clone操作,clone一个对象,越过构造器,在特定使用场景下增加效率。
使用场景:
- 类初始化需要消耗很多资源,比较耗时。
- new方式非常繁琐,还涉及到权限之类的。用clone就减少操作。
- 一个对象需要提供给其他对象使用,而且各个使用都可能改到值,可以考虑用原型模式,做保护性拷贝。
先说说原型模式主要的浅拷贝和深拷贝:
浅拷贝:
可被clone的对象:
- 实现 Cloneable接口
- 重写clone方法
- 先clone,再给参数赋值
public class Document implements Cloneable {
private String mText;
private ArrayList<String> list = new ArrayList<>();
public String getmText() {
return mText;
}
public void setmText(String mText) {
this.mText = mText;
}
@Override
protected Document clone() throws CloneNotSupportedException {
Document document = (Document) super.clone();
document.mText = this.mText;
document.list = this.list;
return document;
}
}
深拷贝:
对于上面ArrayList,它自身实现类cloneable接口的,所以它本身也是可以clone的,对于对象,赋值后,是指向同一个地址的,对于浅拷贝来说,如果改了list对象,那么原有的对象list也会被修改到。所以对于list对象,我们也需要拷贝一下,修改如下。
public class Document implements Cloneable {
private String mText;
private ArrayList<String> list = new ArrayList<>();
public String getmText() {
return mText;
}
public void setmText(String mText) {
this.mText = mText;
}
@Override
protected Document clone() throws CloneNotSupportedException {
Document document = (Document) super.clone();
document.mText = this.mText;
document.list = (ArrayList<String>) this.list.clone();
return document;
}
}
检验
首先:对于浅拷贝
- 我们用下面一段代码来做检验:
Document document = new Document();
document.setmText("11111100000");
document.getList().add("00");
System.out.println("内容原始:" + document.toString());
Document document1 = document.clone();
document1.setmText("111111");
document1.getList().add("111");
System.out.println("内容修改:" + document1.toString());
System.out.println("内容原始:" + document.toString());
- 输出:
内容原始:Document{mText='11111100000', list=[00]}
内容修改:Document{mText='111111', list=[00, 111]}
内容原始:Document{mText='11111100000', list=[00, 111]}
这里很明显的能看到,我们改了String类型,也改了list对象。但是输出后,原始的String类型的text没变,但是list却被改变了。
这是为什么呢?
原因其实是:
String类型是不可变类型,所以我们不推荐在for循环中使用+号来拼接一样,因为每+一次就会创建一块内存来装。同理,这里的虽然拷贝了string类型,但是第二个对象修改的时候,string的text是指向了新的地址而不是把原地址数据给改了,所以原对象的text并没被改。但list就不一样了,改的是原对象地址的数据
所以我们才会需要深拷贝。
然后我们再检验一次深拷贝的:
输出内容:
内容原始:Document{mText='11111100000', list=[00]}
内容修改:Document{mText='111111', list=[00, 111]}
内容原始:Document{mText='11111100000', list=[00]}
我们使用深拷贝的时候,将对象list也clone了,就没有指向的不再是原有地址了,所以改动也就影响不到原来数据咯。
另外:实现Cloneable接口只是一种方式。要实现clone也不一定非要实现这个接口(使用Object来重写clone方法的话一定要实现,不然会抛出异常)。
如果使用new的方式并不耗资源等,clone的时候我们传入自己,用new的效率或许会更高:
public Document(Document document) {
this.mText = document.getmText();
this.list = document.getList();
}
@Override
public Document clone() throws CloneNotSupportedException {
Document document = new Document(this);
return document;
}