`

利用反射进行深层克隆

    博客分类:
  • Java
阅读更多

最近在看《effective java》,其中有一节谈到了克隆,所以今天想来就来研究一下克隆。

 

我们大家都知道,对一个对应进行复制有二种比较好的方式,一种就是序列化,另一种就是克隆。使用序列化进行复制很方便,因为此种方式会自动进行深层复制,只需要我们将要序列化的对象所对应的类实现序列化标示性接口Serializable,它就会将对象里所引用的其他对象一并复制,但此种效率不及Object里的clone克隆方法。不过使用clone进行克隆却是浅复制,它不会自动将对象里所引用的其他对象进行深层克隆,所以如果我们想要进行深层复制时,需要覆写Object中的clone方法,对需要进行深层复制的域进行单独处理,所以应用起来比较麻烦,正是因为这样繁琐,下面我采用了反射的方式来进行深层克隆clone,其只需要克隆的类继承该类DeepClone即可。详细过程请参见注释。

 

深层克隆实现:

/**
 * 
 * 利用反射进行深度克隆,只要继承该类的Bean就具有深度克隆的能力。
 * 
 * 但是不支持克隆父类的属性成员,因为 this.getClass().getDeclaredFields()
 * 只能获取到自己本身所有定义的属性成员,所以此继承的情况下不支持父类的属性成员深
 * 度克隆,除非放弃这种反射,为每个Bean覆写clone方法。
 * 
 * 另外需注意的是,本程序只是对实现了Cloneable接口并重写了clone方法的类实例才进行
 * 深层克隆,如果你的类里含有未实现Cloneable接口的引用类型,则不会帮你进行深层克隆
 * (虽然可以做,比如使用序列化与反序列化来创建另一个实例,但这么做违背了这个类最
 * 初的设计 —— 它本身就是一个不可变类或都是一个不具有状态的如工具类,则创建多个这样
 * 的实例没有什么好处,反而会占用内存与频繁的调用垃圾回器;如果这个类是可变的而没有
 * 实现克隆接口,那么这则是设计人员本身的的设计错误,所以这里不会帮你去克隆这些类)。
 * 
 * 请记住,克隆对那些可变的值类类型的Bean才具有实际意义,对不可变类或者是不具有状态
 * 的类对象克隆没有意义,Java库里的不可变值类型类就是这么处理的,比如String、基本
 * 类型的包装类、BigInteger...,它们都不具有克隆能力
 * 
 * @author jiangzhengjun 2010.5.5
 */
public abstract class DeepClone implements Cloneable, Serializable {

	protected Object clone() throws CloneNotSupportedException {
		Object cloneObj = null;
		try {
			// 克隆对象
			cloneObj = super.clone();

			// 该类的所有属性,包括静态属性
			Field[] filedArr = this.getClass().getDeclaredFields();
			Field field;//属性
			Class fieldType;//属性类型
			Object filedVal;//属性值
			for (int i = 0; i < filedArr.length; i++) {
				field = filedArr[i];
				fieldType = field.getType();
				field.setAccessible(true);
				filedVal = field.get(this);
				/*
				下面代码运行的结果可以表明super.clone()只是浅复制,它只是将原始对象
				的域成员内存地址对拷到了克隆对象中,所以如果是引用类型则指向同一对象,
				若是基本类型,则直接将存储的值复制到克隆对象中,基本类型域成员不需要
				再次单独复制处理。然而,引用类型却是线复制,所以我们需要对引用型单独
				做特殊的复制处理,即深层克隆。
				
				下面是某次的输出结果,从输出结果可以证实上面的结论:					
				i : -1 - -1
				ca : CloneA@480457 - CloneA@480457
				ca1 : CloneA@47858e - CloneA@47858e
				ca2 : CloneA@19134f4 - CloneA@19134f4
				cb : CloneB@df6ccd - CloneB@df6ccd
				sb :  - 
				intArr : [[[I@601bb1 - [[[I@601bb1
				caArr : [[[LCloneA;@1ea2dfe - [[[LCloneA;@1ea2dfe
				cbArr : [[[LCloneB;@17182c1 - [[[LCloneB;@17182c1
				int1Arr : [I@13f5d07 - [I@13f5d07
				ca1Arr : [LCloneA;@f4a24a - [LCloneA;@f4a24a
				cb1Arr : [LCloneB;@cac268 - [LCloneB;@cac268
				*/
				//Field clFiled = cloneObj.getClass().getDeclaredField(
				//		field.getName());
				//clFiled.setAccessible(true);
				//System.out.println(field.getName() + " : " + filedVal + " - "
				//		+ clFiled.get(cloneObj));
				/*
				 * 如果是静态的成员,则不需要深层克隆,因为静态成员属于类成员,
				 * 对所有实例都共享,不要改变现有静态成员的引用指向。
				 * 
				 * 如果是final类型变量,则不能深层克隆,即使复制一份后也不能将
				 * 它赋值给final类型变量,这也正是final的限制。否则在使用反射
				 * 赋值给final变量时会抛异常。所以在我们定义一个引用类型是否是
				 * final时,我们要考虑它是否是真真不需要修改它的指向与指向内 容。
				 */
				if (Modifier.isStatic(field.getModifiers())
						|| Modifier.isFinal(field.getModifiers())) {
					continue;
				}

				//如果是数组
				if (fieldType.isArray()) {
					/*
					 * 克隆数组,但只是克隆第一维,比如是三维,克隆的结果就相当于
					 * new Array[3][][], 即只初始化第一维,第二与第三维 还需进一步
					 * 初始化。如果某个Class对象是数 组类对象,则 class.getComponen
					 * tType返回的是复合类型,即元素的类 型,如 果数组是多维的,那么
					 * 它返回的也是数组类型,类型 比class少一维而已,比如 有
					 * Array[][][] arr = new Array[3][][],则arr.getClass().getC
					 * omponentType返回的为二维Array类型的数组,而且我们可以以这个
					 * 返回的类型来动态创建 三维数组
					 */
					Object cloneArr = Array.newInstance(filedVal.getClass()
							.getComponentType(), Array.getLength(filedVal));

					cloneArr(filedVal, cloneArr);

					// 设置到克隆对象中
					filedArr[i].set(cloneObj, cloneArr);
				} else {// 如果不是数组
					/*
					 * 如果为基本类型或没有实现Cloneable的引用类型时,我们不需要对
					 * 它们做任何克隆处理,因为上面的super.clone()已经对它们进行了
					 * 简单的值拷贝工作了,即已将基本类型的值或引用的地址拷贝到克隆
					 * 对象中去了。super.clone()对基本类型还是属于深克隆,而对引用
					 * 则属于浅克隆。
					 * 
					 * String、 Integer...之类为不可变的类,它们都没有实现Cloneable,
					 * 所以对它们进行浅克隆是没有问题的,即它们指向同一不可变对象是没
					 * 有问题。对不可变对象进行克隆是没有意义的。但要 注意,如果是自己
					 * 设计的类,就要考虑是否实现Cloneable与重 写clone方法,如果没有
					 * 这样作,也会进行浅克隆。
					 * 
					 * 下面只需对实现了Cloneable的引用进行深度克隆。
					 */

					// 如果属性对象实现了Cloneable
					if (filedVal instanceof Cloneable) {

						// 反射查找clone方法
						Method cloneMethod;
						try {
							cloneMethod = filedVal.getClass().getDeclaredMethod("clone",
									new Class[] {});

						} catch (NoSuchMethodException e) {
							cloneMethod = filedVal.getClass().getMethod("clone",
									new Class[] {});
						}
						//调用克隆方法并设置到克隆对象中
						filedArr[i].set(cloneObj, cloneMethod.invoke(filedVal,
								new Object[0]));
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return cloneObj;
	}

	/**
	 * 多维数组深层克隆,如果数组类型是实现了Cloneable接口的某个类,
	 * 则会调用每个元素的clone方法实现深度克隆
	 * 
	 * 虽然数组有clone方法,但我们不能使用反射来克隆数组,因为不能使用
	 * 反射来获取数组的clone方法,这个方法只能通过数组对象本身来调用,
	 * 所以这里使用了动态数组创建方法来实现。
	 * 
	 * @param objArr
	 * @param cloneArr
	 * @throws Exception
	 */
	static private void cloneArr(Object objArr, Object cloneArr) throws Exception {
		Object objTmp;
		Object val = null;
		for (int i = 0; i < Array.getLength(objArr); i++) {
			//注,如果是非数组的基本类型,则返回的是包装类型
			objTmp = Array.get(objArr, i);

			if (objTmp == null) {
				val = null;
			} else if (objTmp.getClass().isArray()) {//如果是数组

				val = Array.newInstance(objTmp.getClass().getComponentType(), Array
						.getLength(objTmp));
				//如果元素是数组,则递归调用
				cloneArr(objTmp, val);
			} else {//否则非数组

				/*
				 * 如果为基本类型或者是非Cloneable类型的引用类型,则直接对拷值 或
				 * 者是对象的地址。没有实现Cloneable的引用类型会实行浅复制, 这对
				 * 于像String不可变类来说是没有关系的,因为它们可以多实例或 多线程
				 * 共享,但如果即没有实现Cloneable,又是可变以的类,浅复制 则会带来
				 * 危险,因为这些类实例不能共享 ,一个实例里的改变会影响到 另一个实
				 * 例。所以在使用克隆方案的时候一定要考虑可变对象的可克隆性,即需要
				 * 实现Cloneable。
				 * 
				 * 注,这里不能使用 objTmp.getClass.isPrimitive()来判断是元素是
				 * 否是基本类型,因为objTmp是通过Array.get获得的,而Array.get返
				 * 回的是Object 类型,也就是说如果是基本类型会自动转换成对应的包
				 * 装类型后返回,所以 我们只能采用原始的类型来判断才行。
				 */
				if (objArr.getClass().getComponentType().isPrimitive()
						|| !(objTmp instanceof Cloneable)) {//基本类型或非Cloneable引用类型
					val = objTmp;
				} else if (objTmp instanceof Cloneable) {//引用类型,并实现了Cloneable
					/*
					 *  用反射查找colone方法,注,先使用getDeclaredMethod获取自
					 *  己类 中所定义的方法(包括该类所声明的公共、保护、默认访问
					 *  及私有的 方法),如果没有的话,再使用getMethod,getMethod
					 *  只能获取公有的方法,但还包括了从父类继承过来的公有方法
					 */
					Method cloneMethod;
					try {
						//先获取自己定义的clone方法
						cloneMethod = objTmp.getClass().getDeclaredMethod("clone",
								new Class[] {});

					} catch (NoSuchMethodException e) {
						//如果自身未定义clone方法,则从父类中找,但父类的clone一定要是public
						cloneMethod = objTmp.getClass()
								.getMethod("clone", new Class[] {});
					}
					cloneMethod.setAccessible(true);
					val = cloneMethod.invoke(objTmp, new Object[0]);

				}
			}
			// 设置克隆数组元素值
			Array.set(cloneArr, i, val);
		}
	}
}

 

深层克隆测试:

//具有克隆能力的测试类
class CloneA implements Cloneable, Serializable {
	int intArr[] = new int[] { 1 };

	protected Object clone() throws CloneNotSupportedException {
		CloneA clone = (CloneA) super.clone();
		clone.intArr = (int[]) intArr.clone();
		return clone;
	}
}

/*
 * 不具有克隆能力的测试类,但该类里有一个引用类型intArr,如果共享则
 * 会有问题,所以该类在克隆的方案里使用(即用在了ValueBean可克隆中)
 * 就是一个错误,这是设计人员自身的错误,问题由设计人员自已负责。
 */
class UnCloneB implements  Serializable{
	int intArr[] = new int[] { 1 };
}

class ParentBean extends DeepClone {
	/*
	 * 使用 new ValueBean().clone()时,
	 * ValueBean的父类ParentBena的属性成员不具有深度克隆的
	 * 能力,但你又不能重写DeepClone父类的clone方法,否则
	 * 反射深层克隆不再起 作用。不知道这个问题能否很好的解
	 * 决。 想了一下,除非不继承自DeepClone,在子类ValueBean
	 * 中重写Object的clone方法,然后在子类中针对该属性做单
	 * 独的克隆处理才可以。
	 */
	public final CloneA pca = new CloneA();
}

/**
 *  用来进行克隆的值Bean
 *  
 *  能克隆的域会进行深层克隆,不能克隆的域会进行浅复制
 */
class ValueBean extends ParentBean {
	private int i = -1;
	private String str = new String("string");
	public static CloneA ca = new CloneA();
	private final CloneA ca1 = new CloneA();
	private CloneA ca2 = new CloneA();
	private UnCloneB cb = new UnCloneB();
	private StringBuffer sb = new StringBuffer();

	//三维数组
	private int[][][] intArr;//基本类型数组
	private CloneA[][][] caArr;//元素实现了Cloneable的数组
	private UnCloneB[][][] cbArr;//元素未实现了Cloneable的数组

	//一维数组
	int[] int1Arr;
	CloneA[] ca1Arr;
	UnCloneB[] cb1Arr;

	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	public ValueBean() {
		intArr = new int[3][][];
		intArr[0] = new int[2][];
		intArr[0][1] = new int[2];
		intArr[0][1][0] = 1;
		intArr[1] = new int[1][];
		intArr[1][0] = new int[2];
		intArr[1][0][0] = 2;
		intArr[1][0][1] = 3;

		caArr = new CloneA[3][][];
		caArr[0] = new CloneA[2][];
		caArr[0][1] = new CloneA[2];
		caArr[0][1][0] = new CloneA();
		caArr[1] = new CloneA[1][];
		caArr[1][0] = new CloneA[2];
		caArr[1][0][0] = new CloneA();
		caArr[1][0][1] = new CloneA();

		cbArr = new UnCloneB[3][][];
		cbArr[0] = new UnCloneB[2][];
		cbArr[0][1] = new UnCloneB[2];
		cbArr[0][1][0] = new UnCloneB();
		cbArr[1] = new UnCloneB[1][];
		cbArr[1][0] = new UnCloneB[2];
		cbArr[1][0][0] = new UnCloneB();
		cbArr[1][0][1] = new UnCloneB();

		int1Arr = new int[2];
		int1Arr[0] = 1;
		int1Arr[1] = 2;

		ca1Arr = new CloneA[3];
		ca1Arr[0] = new CloneA();
		ca1Arr[1] = new CloneA();

		cb1Arr = new UnCloneB[3];
		cb1Arr[0] = new UnCloneB();
		cb1Arr[2] = new UnCloneB();
	}

	public static void main(String[] args) throws Exception {
		ValueBean bt = new ValueBean();
		//因为是静态属性,防止克隆过程中修改,所以先存储起来,供后面对比
		CloneA ca = ValueBean.ca;
		ValueBean btclone = (ValueBean) bt.clone();

		bt.i = 10;
		System.out.println(btclone.i);//-1 ,基本类型克隆成功

		System.out.println(bt.str == btclone.str);//true,String为不可变类,没有克隆

		System.out.println(ca == ValueBean.ca);//true,静态成员没有克隆

		System.out.println(//true,final类型的引用没有深层复制
				bt.ca1 == btclone.ca1);

		System.out.println(//false,可克隆的引用类型已进行深层克隆
				bt.ca2 == btclone.ca2);

		bt.ca2.intArr[0] = 2;//试着改变可克隆原始对象的值
		System.out.println(btclone.ca2.intArr[0]);//1,CloneA里的数组克隆成功

		System.out.println(//true,不可克隆的引用类型还是浅复制
				bt.cb == btclone.cb);

		bt.cb.intArr[0] = 2;//试着改变不可克隆原始对象的值
		System.out.println(btclone.cb.intArr[0]);//2,CloneB里的数组没有深层克隆

		bt.sb.append(1);
		System.out.println(//1,不可克隆引用只进行浅复制,所以指向原始对象
				btclone.sb);

		bt.intArr[0][1][0] = 11;
		bt.intArr[1][0][0] = 22;
		bt.intArr[1][0][1] = 33;
		System.out.println(//11 1,基本类型数组克隆成功
				bt.intArr[0][1][0] + " " + btclone.intArr[0][1][0]);
		System.out.println(//22 2
				bt.intArr[1][0][0] + " " + btclone.intArr[1][0][0]);
		System.out.println(//33 3
				bt.intArr[1][0][1] + " " + btclone.intArr[1][0][1]);
		System.out.println(//null null
				bt.intArr[2] + " " + btclone.intArr[2]);

		//Cloneable引用类型数组克隆成功
		System.out.println(//CloneA@3e25a5 CloneA@19821f
				bt.caArr[0][1][0] + " " + btclone.caArr[0][1][0]);
		System.out.println(//CloneA@addbf1 CloneA@42e816
				bt.caArr[1][0][0] + " " + btclone.caArr[1][0][0]);
		System.out.println(//CloneA@9304b1 CloneA@190d11
				bt.caArr[1][0][1] + " " + btclone.caArr[1][0][1]);
		System.out.println(//null null
				bt.caArr[2] + " " + btclone.caArr[2]);

		bt.caArr[0][1][0].intArr[0] = 2;
		System.out.println(//1,即使原始对象改变了,但这里为深层克隆,所以没影响
				btclone.caArr[0][1][0].intArr[0]);

		// 对象数组本身已克隆,好比直接调用数组的 clone方法。
		System.out.println(//[[[LCloneB;@de6ced [[[LCloneB;@c17164
				bt.cbArr + " " + btclone.cbArr);
		//非Cloneable引用类型数组里克隆后里面的元素指向相同元素
		System.out.println(//CloneB@de6ced CloneB@de6ced
				bt.cbArr[0][1][0] + " " + btclone.cbArr[0][1][0]);
		System.out.println(//CloneB@c17164 CloneB@c17164
				bt.cbArr[1][0][0] + " " + btclone.cbArr[1][0][0]);
		System.out.println(//CloneB@1fb8ee3 CloneB@1fb8ee3
				bt.cbArr[1][0][1] + " " + btclone.cbArr[1][0][1]);
		System.out.println(//null null
				bt.cbArr[2] + " " + btclone.cbArr[2]);

		bt.cbArr[0][1][0].intArr[0] = 2;
		System.out.println(//2,原始对象改变影响到另一实例,因为UnCloneB不具克隆能力
				btclone.cbArr[0][1][0].intArr[0]);

		//一维数组克隆也是没有问题的
		bt.int1Arr[0] = 11;
		bt.int1Arr[1] = 22;
		System.out.println(//11 1
				bt.int1Arr[0] + " " + btclone.int1Arr[0]);
		System.out.println(//22 2
				bt.int1Arr[1] + " " + btclone.int1Arr[1]);

		System.out.println(//CloneA@ca0b6 CloneA@10b30a7
				bt.ca1Arr[0] + " " + btclone.ca1Arr[0]);
		System.out.println(//CloneA@1a758cb CloneA@1b67f74
				bt.ca1Arr[1] + " " + btclone.ca1Arr[1]);

		System.out.println(//CloneB@69b332 CloneB@69b332
				bt.cb1Arr[0] + " " + btclone.cb1Arr[0]);
		System.out.println(//null null
				bt.cb1Arr[1] + " " + btclone.cb1Arr[1]);

		//父类的属性成员没有被深度克隆
		System.out.println(bt.pca == btclone.pca);//true

		//如果直接创建父类的实例然后对它进行克隆,这与直接创建子类一样也是可以的
		ParentBean pb1 = new ParentBean();
		ParentBean pb2 = (ParentBean) pb1.clone();
		System.out.println(pb1.pca == pb2.pca);//false
	}
}

 

先就写到这里吧,以后有时间来使用序列化来实现深层克隆。

分享到:
评论
10 楼 xjkwq1qq 2010-10-28  
<p>学习了,谢楼主分享!</p>
<p>但是仔细分析后发现按楼主的方法还是有些问题,除非是所有想要克隆的对象都继承DeepClone,否则难以实现完全深克隆</p>
<p>分析下代码:通过反射获得的clone方法就未必是DeepClone中重写的方法</p>
<pre name="code" class="java"> try {  
                            cloneMethod = filedVal.getClass().getDeclaredMethod("clone",  
                                    new Class[] {});  
 
                        } catch (NoSuchMethodException e) {  
                            cloneMethod = filedVal.getClass().getMethod("clone",  
                                    new Class[] {});  
                        }  
</pre>
<p>那么需要深克隆,每个需要克隆的对象都需要继续DeepClone也不现实</p>
<p> </p>
<p>下面是我将楼主的代码稍做修改,改成静态的工具方法(前提:需要克隆的对象除了要实现Cloneable接口,还必须重写clone方法):</p>
<pre name="code" class="java">public class DeepClone {
public static Object clone(Cloneable obj) throws IllegalArgumentException,IllegalAccessException, SecurityException, NoSuchMethodException,InvocationTargetException {
// 验证传入参数不为null
if (obj == null) {
return null;
}

Object cloneObj = null;
Method cloneMethod = null;
// 获取浅克隆对象
try {
cloneMethod = obj.getClass().getDeclaredMethod("clone",new Class[] {});
} catch (Exception e) {
cloneMethod = obj.getClass().getMethod("clone", new Class[] {});
}
cloneMethod.setAccessible(true);
cloneObj = cloneMethod.invoke(obj, new Object[0]);

Field[] fieldArr = obj.getClass().getDeclaredFields();
int fieldLength = fieldArr.length;
for (int i = 0; i &lt; fieldLength; i++) {
// 获得属性对象的信息
Field field = fieldArr[i];
Class fieldType = field.getType();
field.setAccessible(true);
Object fieldValue = field.get(obj);

// 如果属性为静态或者final或者其值为null,那么不做处理
if (fieldValue == null || Modifier.isStatic(field.getModifiers())
|| Modifier.isFinal(field.getModifiers())) {
continue;
}

if (fieldType.isArray()) {// 对象为数组
Object cloned = arrClone(fieldValue);
field.set(cloneObj, cloned);
} else {// 对象不为数组
if (fieldValue instanceof Cloneable) {
Object cloned = clone((Cloneable) fieldValue);
field.set(cloneObj, cloned);
}
}
}
return cloneObj;
}

public static Object arrClone(Object objArr)throws IllegalArgumentException, SecurityException,IllegalAccessException, NoSuchMethodException,InvocationTargetException {
// 验证传入参数不为null
if (objArr == null) {
return null;
}
int arrLength = Array.getLength(objArr);
// 创建一个新的数组对象
Object cloneArr = Array.newInstance(objArr.getClass().getComponentType(), arrLength);
// 遍历数组的每个成员
for (int i = 0; i &lt; arrLength; i++) {
Object sub = Array.get(objArr, i);
if (sub == null) {
continue;
}
Object cloned = null;
if (sub.getClass().isArray()) {// 对象为数组
cloned = arrClone(sub);

} else {// 对象不为数组
if(objArr.getClass().getComponentType().isPrimitive()||!(sub instanceof Cloneable)){//基本类型或
cloned=sub;
}else if (sub instanceof Cloneable) {
cloned = clone((Cloneable) sub);

}
}
Array.set(cloneArr, i, cloned);
}
return cloneArr;
}
}
</pre>
<p> </p>
<p>采用了一下递归的思想来确保需要克隆的对象都是深克隆,各位指点下</p>
9 楼 junJZ_2008 2010-05-07  
torycatkin 写道
为什么不用ObjectStream进行深克隆,而要自己写反射????

学习学习而已,也说不上为什么?序列化不用怎么写就可以做到,没什么劲。不过你硬要我说出一点就是clone比序列化要快吧,哪怕是在用反射的情况下,如果不用反射我想更快吧
8 楼 torycatkin 2010-05-07  
为什么不用ObjectStream进行深克隆,而要自己写反射????
7 楼 junJZ_2008 2010-05-07  
xtlincong 写道
利用反射来克隆,怎么解决环依赖的问题?
最常见的是hibernate的父子相互依赖
不瞎折腾还是序列化方便。不过更好的方式可以参考blazeds。
java序列化到as,as序列化到java。都不依赖于Serializable


这只是一种技术,实现的方式有很多种,这一种当然不一定是最好的,但当你把这些技术弄清后,我想没有你解次不了的问题。这里我只是使用了克隆的方式来实现 ,序列化当然更简单,当然可能也还有其他的办法,就像你说的使用blazeds技术也不是不可以。
我是抱着一种学习的态度去写的这个东西,不一定适合真真的应用中,但这不代表我们不去学这里基本性的东西,一旦掌握了它们,使用其他人写的组件我想也会理解得更好。
6 楼 xtlincong 2010-05-07  
利用反射来克隆,怎么解决环依赖的问题?
最常见的是hibernate的父子相互依赖
不瞎折腾还是序列化方便。不过更好的方式可以参考blazeds。
java序列化到as,as序列化到java。都不依赖于Serializable
5 楼 junJZ_2008 2010-05-06  
做了个简单的测试:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
	private final static ValueBean vb = new ValueBean();


	public static void main(String[] args) throws Exception {
		testClone(1);
		testSerial(1);
		
		testClone(100);
		testSerial(100);
	}

	static void testSerial(int times) throws IOException, ClassNotFoundException {
		ByteArrayOutputStream bos;
		ObjectOutputStream oos;
		ByteArrayInputStream bin;
		ObjectInputStream ois;
		long startTime = System.currentTimeMillis();

		for (int i = 0; i < times; i++) {
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			oos.writeObject(vb);
			bin = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bin);
			ois.readObject();
		}

		System.out.println("testSerial:" + (System.currentTimeMillis() - startTime));
	}

	static void testClone(int times) throws CloneNotSupportedException {
		long startTime = System.currentTimeMillis();

		for (int i = 0; i < times; i++) {
			Test.vb.clone();
		}
		System.out.println("testClone:" + (System.currentTimeMillis() - startTime));
	}
}



某次输出结果:
testClone:2
testSerial:45
testClone:20
testSerial:196

好像还是比序列化方式的要快,如果不使用反射我想会更快!
4 楼 onkyo 2010-05-06  
<div class="quote_title">junJZ_2008 写道</div>
<div class="quote_div">我们大家都知道,对一个对应进行复制有二种比较好的方式,一种就是序列化,另一种就是克隆。使用序列化进行复制很方便,因为此种方式会自动进行深层复制,只需要我们将要序列化的对象所对应的类实现序列化标示性接口Serializable,它就会将对象里所引用的其他对象一并复制,但此种效率不及 Object里的clone克隆方法。不过使用clone进行克隆却是浅复制,它不会自动将对象里所引用的其他对象进行深层克隆,所以如果我们想要进行深层复制时,需要覆写Object中的clone方法,对需要进行深层复制的域进行单独处理,所以应用起来比较麻烦,正是因为这样繁琐,下面我采用了反射的方式来进行深层克隆clone,其只需要克隆的类继承该类DeepClone即可。</div>
<p> </p>
<p>Object 里面的clone方法之所以快, 可能就是因为它是浅克隆, 如果用反射的方法来做深克隆的话, 个人觉得效率应该要比序列化差很多。 那就完全失去了这么做的意义了。 如果楼主能提供些branchmark的数据, 来比较一下序列化和反射两种深度克隆的性能, 会更有说服力。</p>
<p> </p>
3 楼 junJZ_2008 2010-05-06  
wendal 写道
有点意思

不过,我认为需要对String进行区别对待.
还是就是,搞不懂,为啥一定要继承你的类呢?这样通用性很差啊...


String为什么要区别对待?String本身就是不可变对象 ,这里根就没有对它进克隆,因为String没有实现Cloneable接口,程序不会对它再次克隆 。

不继承也可以啊,你总要有个地方来写入这些代码吧。
2 楼 wendal 2010-05-06  
有点意思

不过,我认为需要对String进行区别对待.
还是就是,搞不懂,为啥一定要继承你的类呢?这样通用性很差啊...
1 楼 抛出异常的爱 2010-05-06  
有点像在看jquery的原码。。。。。

相关推荐

    Thinking in Java 中文第四版+习题答案

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    Think in Java(中文版)chm格式

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变...

    JAVA_Thinking in Java

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    Java初学者入门教学

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    ThinkInJava

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    java 编程入门思考

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    thinkinjava

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    Thinking in Java简体中文(全)

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    java联想(中文)

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    Thinking in Java(中文版 由yyc,spirit整理).chm

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    JAVA_Thinking in Java(中文版 由yyc,spirit整理).chm

    12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...

    Java开发实战1200例(第1卷).(清华出版.李钟尉.陈丹丹).part3

    实例160 利用反射重写toString()方法 208 实例161 反射与动态代理 209 7.3 常见的未检查型异常 210 实例162 算数异常 210 实例163 数组存值异常 211 实例164 数组下标越界异常 212 实例165 空指针异常 213 7.4 常见...

Global site tag (gtag.js) - Google Analytics