【JAVA】如何实现对象的深度克隆 - 木东驿站 - Powered by MoodBlog

CONTENT

【JAVA】如何实现对象的深度克隆

在JAVA中,一个对象拥有自身的属性,如果我们把对象A赋予另一个对象变量B,并不会把对象A的内容传给对象变量B,而是在那个对象变量B里标记上对象A的地址,再次调用时,还是调用的对象A本来的属性和方法。

有时候,我们需要复制一份和对象A完全一样的对象B,然后在操作B的同时不影响A。比如在我之前的一个游戏《梦战》中,有一个玩法是捕捉召唤兽,一旦捕捉成功,召唤兽就会放到自己这边的仓库,即使战斗消失,敌方数据全部释放,你的召唤兽依旧存在。这里就需要将你捕捉的敌人复制一份,放到自己这边的仓库。这种复制操作,我们称为深度克隆。

既然有深度克隆,那么有浅克隆吗?当然有。如果你的对象属性中有引用类型,浅克隆只能克隆基础类型,无法继续深层克隆引用类型,只能复制一下那些对象的引用地址

Clone

java的万物父类Object提供了clone方法,可以实现克隆,不过这个方法是protected修饰的,子类无法直接使用,需要重写成public方法。同时,我们要给拥有clone方法的类标志一下Cloneable接口,具体原因这里不再分析。有兴趣的同学可以看这篇分析:http://onlylove.iteye.com/blog/265113

我们编写一个Animal类,来测试clone功能

public class DateList  {
	public static void main(String args[]){
		Animal A = new Animal();
		A.kind = 1;
		A.name = "小猫";
		A.animal = new Animal();
		A.animal.name = "小狗";
		
		Animal B = (Animal)A.clone();
		B.kind = 2;
		System.out.println(A.kind);
		//输出1 没有改变A的属性,说明kind确实是复制过来的,不是引用
		B.name = "小牛";
		System.out.println(A.name);
		//输出小猫 没有改变A的属性,说明name确实是复制过来的,不是引用
		B.animal.name = "小牛";
		System.out.println(A.animal.name);
		//输出小牛 改变了A的属性,说明A.animal并没有复制过来,而是复制的A.animal的地址
		
	}
}
class Animal implements Cloneable{
	public int kind;
	public String name;
	public Animal animal;
	public Object clone(){
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			return null;
		}
	}
}

我们发现基础变量kind是完全克隆过来了,String类型的name也克隆过来了,但是animal只是克隆了一个引用,或者称原对象的地址。那么我们会产生疑问,到底clone是克隆的什么呢?基础变量吗?String难道不是引用类型吗?

其实String类型是一个例外,也可以实现像基础变量那样直接复制,因为String被sun公司的工程师写成了一个不可更改的类(immutable class),在所有string类中的函数都不能更改自身的值。所以只要类是immutable class,clone也会照样把数据复制过来

那问题来了,我们总不能把每个类都设置成不可更改吧,那程序还怎么运作?实际上无需如此,我们只要这样写clone方法,就可以实现引用类型的复制了。

class Animal implements Cloneable{
	public int kind;
	public String name;
	public Animal animal;
	public Object clone(){
		try {
			Animal temp = (Animal)super.clone();
			if(this.animal!=null){
				temp.animal = (Animal)this.animal.clone();
			}
			return temp;
		} catch (CloneNotSupportedException e) {
			return null;
		}
	}
}


对象流实现深度克隆

每个clone方法都像这么写,给引用类型单独复制,我们不得累死?幸好JAVA有序列化机制,只要类是可序列化的,我们就可以把对象序列化到内存中,然后再读回产生新对象,这样产生的新对象就是对原有对象的深度克隆。为了方便,我们在clone方法中实现这种序列化克隆方式,所以我们要给对应类标记Cloneable和Serialization接口,不然会有异常抛出(标记接口无实质性作用,只是一种规定,标记该类拥有xx功能)。

class Animal implements Cloneable,Serializable{
	public int kind;
	public String name;
	public Animal animal;
	public Object clone(){
		try {
			ByteArrayOutputStream baos =  new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(this);
			oos.close();
			
			ByteArrayInputStream bais =  new ByteArrayInputStream(baos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bais);
			Object temp = ois.readObject();
			return temp;
		} catch (Exception e) {
			return null;
		}
	}
}

这种方法尽管很好用,属于万能型克隆/拷贝方式,但是效率非常低下,所以要尽可能的避免调用,在仅仅拷贝数据无法完成功能的情况下再考虑这种深度克隆。


个快快 2017年11月09日 天气 晴

REMARKS

© 2018 MoodBlog 0.2 个快快 作品 | 参考主题: mathilda by fuzzz. | 鲁ICP备16047814号