likes
comments
collection
share

解析原型模式及深浅拷贝

作者站长头像
站长
· 阅读数 21

简介

原型模式是一个创建型设计模式,原型表明这个模式有一个样板实例,用户从这个实例对象中复制一个内部属性一致的对象,也就是我们通常说的克隆,被复制的对象就是原型。原型模式常用于创建复杂的或者构造耗时的实例,复制一个已经存在的实例可以使得程序运行更加高效。

类关系

解析原型模式及深浅拷贝

  • 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
评论
请登录