解析原型模式及深浅拷贝
简介
原型模式是一个创建型设计模式,原型表明这个模式有一个样板实例,用户从这个实例对象中复制一个内部属性一致的对象,也就是我们通常说的克隆,被复制的对象就是原型。原型模式常用于创建复杂的或者构造耗时的实例,复制一个已经存在的实例可以使得程序运行更加高效。
类关系
- Client:客户端用户
- Prototype:抽象类或者接口,声明具备clone能力
- ConcretePrototype:具体的原型类
原型模式是一个很简单的模式,实现一个接口,重写一个方法即完成了原型模式。当一个对象创建过程很复杂,比如创建对象需要好多个字段,此时,我们可以根据已有的对象进行拷贝,而不必关心创建的细节。
简单实现
Cloneable接口及其实现类
首先原型类需要实现java.lang.Cloneable
接口,之后重写Object中的clone()
方法。Cloneable是一个标记接口,只有实现了这个接口的类才可以通过clone进行对象的拷贝。如果不实现这个接口,则会抛出异常
现在我们编写一个原型类并实现Cloneable接口
//实现Cloneable接口
public class Document implements Cloneable{
private String mText;
private String mText2;
private String mText3;
private String mText4;
//一个引用类型的变量
private UserInfo userInfo;
//构造函数需要的字段有很多
public Document(String mText, String mText2, String mText3, String mText4, UserInfo userInfo) {
super();
this.mText = mText;
this.mText2 = mText2;
this.mText3 = mText3;
this.mText4 = mText4;
this.userInfo = userInfo;
}
//clone方法 返回super.clone()返回的对象
@Override
protected Object clone() throws CloneNotSupportedException {
Document document = (Document)super.clone();
return document;
}
@Override
public String toString() {
return "Document [hashCode="+this.hashCode()+", "+"mText=" + mText + ", mText2=" + mText2 + ", mText3=" + mText3 + ", mText4=" + mText4
+ ", userInfo=" + userInfo +"]";
}
...
引用类UserInfo(用于演示引用类型变量)
public class UserInfo implements Cloneable{
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "UserInfo [name=" + name + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
通过Document
这个类可以看到,由于其构造函数所需要的参数很多,要构造一个对象的成本是很大的,所以对于这种情况,可以考虑使用clone来创建对象,可以节省很多成本。
Client实现
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
UserInfo userInfo = new UserInfo();
userInfo.setName("xxx");
//创建Document实例对象
Document document = new Document("1","2","3","4",userInfo);
//得到document的克隆对象
Document clone = (Document)document.clone();
System.out.println("document="+document);
System.out.println("clone="+clone);
}
}
通过document.clone()
方法很简单的就可以得到一个克隆好的对象,查看一下打印的结果
document=Document [hashCode=2018699554, mText=1, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [name=xxx]]
clone=Document [hashCode=1311053135, mText=1, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [name=xxx]]
可以看到clone对象和原对象的hashCode值是不同的,也就是说这是两个不同的对象。通过这种方式可以很方便的去创建对象。
深拷贝和浅拷贝
浅拷贝
如果对象中有引用类型,比如UserInfo,当我们试图去修改这个引用时,可能会引发一些问题,具体来看一下。现在修改一下克隆对象的值,然后输出一下
public static void main(String[] args) throws CloneNotSupportedException {
UserInfo userInfo = new UserInfo();
userInfo.setName("xxx");
Document document = new Document("1","2","3","4",userInfo);
//对原始对象的String类型进行修改
document.setmText("text1");
//得到克隆对象
Document clone = (Document)document.clone();
//修改克隆对象的引用数据类型userInfo
clone.getUserInfo().setName("yyy");
//修改克隆对象的String类型变量
clone.setmText("text2");
System.out.println("document="+document);
System.out.println("clone="+clone);
}
以上代码我通过clone方法得到了克隆对象,同时修改了克隆对象的userinfo的内容,并同时修改了原始对象和克隆对象的String类型做了修改。产看一下结果
document=Document [hashCode=2018699554, mText=text1, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [name=yyy]]
clone=Document [hashCode=1311053135, mText=text2, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [name=yyy]]
看到问题了吗,我只是修改了克隆对象的userinfo,但是原始对象的userinfo也被修改了,从这里我们可以发现,对于引用类型的变量,克隆对象和原始对象指向的是同一块内存空间,当对象发生改变,原始对象和克隆对象都会发生相应的改变,我们修改一下UserInfo的打印,将其内存地址一并输出
UserInfo.java
@Override
public String toString() {
return "UserInfo [hashcode="+this.hashCode()+", "+"name=" + name + "]";
}
再次执行Client的打印
document=Document [hashCode=2018699554, mText=text1, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [hashcode=1311053135, name=yyy]]
clone=Document [hashCode=118352462, mText=text2, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [hashcode=1311053135, name=yyy]]
可以看到,document的userinfo和clone对象的userinfo指向的是同样的地址。
深拷贝
对于这样的问题应该如何解决呢?我们修改一下Document的clone方法
Document.java
@Override
protected Object clone() throws CloneNotSupportedException {
Document document = (Document)super.clone();
//对于document的引用类型进行clone的操作
document.setUserInfo((UserInfo)userInfo.clone());
return document;
}
通过以上的修改,我们再来执行一下打印的操作
document=Document [hashCode=2018699554, mText=text1, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [hashcode=1311053135, name=xxx]]
clone=Document [hashCode=118352462, mText=text2, mText2=2, mText3=3, mText4=4, userInfo=UserInfo [hashcode=1550089733, name=yyy]]
看到了吧,这次的打印不同了,只有克隆对象的值修改了,而原始对象的值并没有被修改,同时,他们所指向的userinfo的地址也不同了,这就是深拷贝。
小结
我们通过一个实例来认识了原型模式,同时又通过对一个引用类型变量的修改,来理解深拷贝和浅拷贝的含义,这里再来总结一下两者的概念。
浅拷贝:就是把原型对象中成员变量为值类型的属性都赋值给克隆对象,把原型对象中的成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为饮用对象,则此引用对象的地址是共享给原型对象和克隆对象的。浅拷贝只会复制原型对象,但不会复制其所引用的对象。
深拷贝:将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,深拷贝会把原型对象和原型对象所引用的��象,都复制一份给克隆对象。
转载自:https://juejin.cn/post/6983851154824232968