java对象拷贝

10 篇文章 0 订阅

Java语言中没有明确的指针定义,实质上每一个new语句返回的都是一个指针的引用,只不过在大多时候不用关心如何操作这个“指针”,我们唯一要关心的是在给函数传递对象的时候。

Java对对象和基本的数据类型的处理是不一样的。和C语言一样,当把Java的基本数据类型(如int,char,double等)作为入口参数传给函数体的时候,传入的参数在函数体内部变成了局部变量,这个局部变量是输入参数的一个拷贝,所有的函数体内部的操作都是针对这个拷贝的操作,函数执行结束后,这个局部变量也就完成了它的使命,它影响不到作为输入参数的变量。这种方式的参数传递被称为“值传递”。而在Java中用对象的作为入口参数的传递则缺省为“引用传递”,也就是说仅仅传递了对象的一个“引用”,这个“引用”的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。除了在函数传值的时候是“引用传递”,在任何用“=”向对象变量赋值的时候都是“引用传递”。
 

在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,实现clone()方法是最简单,也是最高效的手段。

Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

使用clone()的典型示例:

class CloneClass implements Cloneable {
	public int intValue;

	public Object clone() {
		CloneClass obj = null;
		try {
			obj = (CloneClass) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return obj;
	}
}


有三个值得注意的地方,一是实现拷贝功能的CloneClass类需要实现Cloneable接口;二是CloneClass类重写了clone()方法;最后在重写的clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。

仔细观察一下Object类的clone()是一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。另外,也要观察Object类中的clone()还是一个protected属性的方法,这也意味着如果要应用clone()方法必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。

那么实现拷贝功能的类为什么还要实现Cloneable接口呢?其实Cloneable接口是不包含任何方法的,这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。
 

以上是clone的最基本的步骤,想要完成一个成功的clone,还要了解什么是“影子clone”和“深度clone”。


 

什么是影子clone?

下面的例子包含三个类CloneA,CloneB,CloneMain。CloneB类包含了一个CloneA的实例和一个int类型变量,并且重载clone()方法。CloneMain类初始化CloneB类的一个实例cloneB1,然后调用clone()方法生成了一个cloneB1的拷贝cloneB2。最后考察一下cloneB1和cloneB2的输出:

影子clone示例:


class CloneA {
	private int intValue;

	public CloneA(int value) {
		intValue = value;
	}

	public void doubleValue() {
		intValue *= 2;
	}

	public String toString() {
		return Integer.toString(intValue);
	}
}

class CloneB implements Cloneable {
	public int intValue;
	public CloneA cloneA = new CloneA(111);

	public Object clone() {
		CloneB obj = null;
		try {
			obj = (CloneB) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return obj;
	}
}

public class CloneMain {
	public static void main(String[] a) {
		CloneB cloneB1 = new CloneB();
		cloneB1.intValue = 11;
		System.out.println("拷贝前cloneB1.intValue = " + cloneB1.intValue);
		System.out.println("拷贝前cloneB1.cloneA = " + cloneB1.cloneA);

		CloneB cloneB2 = (CloneB) cloneB1.clone();
		cloneB2.intValue = 22;
		cloneB2.cloneA.doubleValue();
		System.out.println("============================");
		System.out.println("拷贝后cloneB1.intValue = " + cloneB1.intValue);
		System.out.println("拷贝前cloneB1.cloneA = " + cloneB1.cloneA);
		System.out.println("============================");
		System.out.println("拷贝后cloneB2.intValue = " + cloneB2.intValue);
		System.out.println("拷贝前cloneB2.cloneA = " + cloneB2.cloneA);
	}
}


执行结果:


拷贝前cloneB1.intValue = 11
拷贝前cloneB1.cloneA = 111
============================
拷贝后cloneB1.intValue = 11
拷贝后cloneB1.cloneA = 222
============================
拷贝后cloneB2.intValue = 22
拷贝后cloneB2.cloneA = 222


输出的结果表明int类型的变量intValue和CloneA的实例对象cloneA的clone结果不一致,int类型是真正的被clone了,因为改变了cloneB2中的intValue变量,对cloneB1的intValue没有产生影响,也就是说,cloneB2.intValue与cloneB1.intValue已经占据了不同的内存空间,b2.intValue是b1.intValue的一个真正拷贝。相反,对cloneB2.cloneA的改变同时改变了cloneB1.cloneA,很明显 ,cloneB2.cloneA和cloneB1.cloneA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为“影子clone”。要想让cloneB2.cloneA指向与cloneB1.cloneA不同的对象,而且cloneB2.cloneA中还要包含cloneB1.cloneA中的信息作为初始信息,就要实现“深度clone”。


 

怎么进行深度clone?

把上面的例子改成深度clone只需要两个改变:一是让CloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重写clone()方法);二是在CloneB的clone()方法中加入一句obj.cloneA = (CloneA) cloneA.clone();

修改后的深度clone程序如下:


class CloneA implements Cloneable {
	private int intValue;

	public CloneA(int value) {
		intValue = value;
	}

	public void doubleValue() {
		intValue *= 2;
	}

	public String toString() {
		return Integer.toString(intValue);
	}

	public Object clone() {
		CloneA obj = null;
		try {
			obj = (CloneA) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return obj;
	}
}

class CloneB implements Cloneable {
	public int intValue;
	public CloneA cloneA = new CloneA(111);

	public Object clone() {
		CloneB obj = null;
		try {
			obj = (CloneB) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		obj.cloneA = (CloneA) cloneA.clone();
		return obj;
	}
}

public class CloneMain {
	public static void main(String[] a) {
		CloneB cloneB1 = new CloneB();
		cloneB1.intValue = 11;
		System.out.println("拷贝前cloneB1.intValue = " + cloneB1.intValue);
		System.out.println("拷贝前cloneB1.cloneA = " + cloneB1.cloneA);

		CloneB cloneB2 = (CloneB) cloneB1.clone();
		cloneB2.intValue = 22;
		cloneB2.cloneA.doubleValue();
		System.out.println("============================");
		System.out.println("拷贝后cloneB1.intValue = " + cloneB1.intValue);
		System.out.println("拷贝前cloneB1.cloneA = " + cloneB1.cloneA);
		System.out.println("============================");
		System.out.println("拷贝后cloneB2.intValue = " + cloneB2.intValue);
		System.out.println("拷贝前cloneB2.cloneA = " + cloneB2.cloneA);
	}
}

执行结果:


拷贝前cloneB1.intValue = 11
拷贝前cloneB1.cloneA = 111
============================
拷贝后cloneB1.intValue = 11
拷贝后cloneB1.cloneA = 111
============================
拷贝后cloneB2.intValue = 22
拷贝后cloneB2.cloneA = 222

可以看出,现在cloneB2.cloneA的改变对cloneB1.cloneA没有产生影响。此时cloneB1.cloneA与cloneB2.cloneA指向了两个不同的CloneA实例。
要知道不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的CloneA类型变量改成StringBuffer类型,看一下JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中将原来的obj.cloneA= (CloneA)cloneA.clone();改为obj.cloneA= new StringBuffer(cloneA.toString());(假设是SringBuffer对象,而且变量名仍是cloneA)。
还要知道的是除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。


 

使用对象流实现对象的深度clone

将对象序列化成流,因为写在流里的是对象的一个clone,而原对象仍然存在于内存中,所以利用这个特性可以实现对象的深度clone。

代码实现:



public class Test {
	public static Object deepCopy(Object object) {
		Object obj = null;
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		ObjectInputStream ois = null;
		ByteArrayInputStream bis = null;
		try {
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			oos.writeObject(object);
			bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);
			obj = ois.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			close(bos);
			close(oos);
			close(ois);
			close(bis);
		}
		return obj;
	}

	private static void close(Closeable stream) {
		if (stream != null)
			try {
				stream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
	}

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<String, String>();
		map.put("1", "1");
		@SuppressWarnings("unchecked")
		Map<String, String> map1 = (Map<String, String>) deepCopy(map);
		System.out.println(map1.get("1"));
	}
}










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值