原文 :http://blog.csdn.net/naughty610/article/details/6598943
http://www.cnblogs.com/yxnchinahlj/archive/2010/09/20/1831615.html
1.浅复制与深复制概念
⑴浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不
复制它所引用的对象。
⑵深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原
有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
2.Java的clone()方法
⑴clone方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:
①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
⑵Java中对象的克隆
①为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。
②在派生类中覆盖基类的clone()方法,并声明为public。
③在派生类的clone()方法中,调用super.clone()。
④在派生类中实现Cloneable接口。
一个很典型的调用clone()代码如下:
- public class CloneClass implements Cloneable {
- public int aInt;
- public Object clone() {
- CloneClass o = null;
- try {
- o = (CloneClass) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
- }
应该说第三点是最重要的,仔细观察一下Object类的clone()一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。
那么clone类为什么还要实现Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。
以上是clone的最基本的步骤,想要完成一个成功的clone,还要了解什么是"影子clone"和"深度clone"。
什么是影子clone?
- package com.zoer.src;
- class UnCloneA {
- private int i;
- public UnCloneA(int ii) {
- i = ii;
- }
- public void doublevalue() {
- i *= 2;
- }
- public String toString() {
- return Integer.toString(i);
- }
- }
- class CloneB implements Cloneable {
- public int aInt;
- public UnCloneA unCA = new UnCloneA(111);
- public Object clone() {
- CloneB o = null;
- try {
- o = (CloneB) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
- }
- public class ObjRef {
- public static void main(String[] a) {
- CloneB b1 = new CloneB();
- b1.aInt = 11;
- System.out.println("before clone,b1.aInt = " + b1.aInt);
- System.out.println("before clone,b1.unCA = " + b1.unCA);
- CloneB b2 = (CloneB) b1.clone();
- b2.aInt = 22;
- b2.unCA.doublevalue();
- System.out.println("=================================");
- System.out.println("after clone,b1.aInt = " + b1.aInt);
- System.out.println("after clone,b1.unCA = " + b1.unCA);
- System.out.println("=================================");
- System.out.println("after clone,b2.aInt = " + b2.aInt);
- System.out.println("after clone,b2.unCA = " + b2.unCA);
- }
- }
输出结果:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 222
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222
输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。
怎么进行深度clone?
把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();
- package com.zoer.src;
- class UnCloneA implements Cloneable {
- private int i;
- public UnCloneA(int ii) {
- i = ii;
- }
- public void doublevalue() {
- i *= 2;
- }
- public String toString() {
- return Integer.toString(i);
- }
- public Object clone() {
- UnCloneA o = null;
- try {
- o = (UnCloneA) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
- }
- class CloneB implements Cloneable {
- public int aInt;
- public UnCloneA unCA = new UnCloneA(111);
- public Object clone() {
- CloneB o = null;
- try {
- o = (CloneB) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- o.unCA = (UnCloneA) unCA.clone();
- return o;
- }
- }
- public class CloneMain {
- public static void main(String[] a) {
- CloneB b1 = new CloneB();
- b1.aInt = 11;
- System.out.println("before clone,b1.aInt = " + b1.aInt);
- System.out.println("before clone,b1.unCA = " + b1.unCA);
- CloneB b2 = (CloneB) b1.clone();
- b2.aInt = 22;
- b2.unCA.doublevalue();
- System.out.println("=================================");
- System.out.println("after clone,b1.aInt = " + b1.aInt);
- System.out.println("after clone,b1.unCA = " + b1.unCA);
- System.out.println("=================================");
- System.out.println("after clone,b2.aInt = " + b2.aInt);
- System.out.println("after clone,b2.unCA = " + b2.unCA);
- }
- }
输出结果:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 111
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222
可以看出,现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例,而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值,在这里,b1.i = b2.i = 11。
要知道不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的UnCloneA类型变量改成StringBuffer类型,看一下JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();
还要知道的是除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。
Clone中String和StringBuffer的区别
应该说明的是,这里不是着重说明String和StringBuffer的区别,但从这个例子里也能看出String类的一些与众不同的地方。
下面的例子中包括两个类,CloneC类包含一个String类型变量和一个StringBuffer类型变量,并且实现了clone()方法。在StrClone类中声明了CloneC类型变量c1,然后调用c1的clone()方法生成c1的拷贝c2,在对c2中的String和StringBuffer类型变量用相应的方法改动之后打印结果:
- package com.zoer.src;
- class CloneC implements Cloneable {
- public String str;
- public StringBuffer strBuff;
- public Object clone() {
- CloneC o = null;
- try {
- o = (CloneC) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return o;
- }
- }
- public class StrClone {
- public static void main(String[] a) {
- CloneC c1 = new CloneC();
- c1.str = new String("initializeStr");
- c1.strBuff = new StringBuffer("initializeStrBuff");
- System.out.println("before clone,c1.str = " + c1.str);
- System.out.println("before clone,c1.strBuff = " + c1.strBuff);
- CloneC c2 = (CloneC) c1.clone();
- c2.str = c2.str.substring(0, 5);
- c2.strBuff = c2.strBuff.append(" change strBuff clone");
- System.out.println("=================================");
- System.out.println("after clone,c1.str = " + c1.str);
- System.out.println("after clone,c1.strBuff = " + c1.strBuff);
- System.out.println("=================================");
- System.out.println("after clone,c2.str = " + c2.str);
- System.out.println("after clone,c2.strBuff = " + c2.strBuff);
- }
- }
执行结果:
- before clone,c1.str = initializeStr
- before clone,c1.strBuff = initializeStrBuff
- =================================
- after clone,c1.str = initializeStr
- after clone,c1.strBuff = initializeStrBuff change strBuff clone
- =================================
- after clone,c2.str = initi
- after clone,c2.strBuff = initializeStrBuff change strBuff clone
- </span></span>
打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java把Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.str与c2.str仍然是引用,而且都指向了同一个String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String被Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。
3.利用串行化来做深复制
把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。
应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
如下为深复制源代码。
public Object clone() {
// 将对象写到流里
ByteArrayOutoutStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
// 从流里读出来
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
这样做的前提是对象以及对象内部所有引用到的对象都是 可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。
class Student implements Serializable {
String name;// 常量对象
int age;
Teacher t;// 学生1和学生2的引用值都是一样的。
Student(String name, int age, Teacher t) {
this.name = name;
this.age = age;
this.p = p;
}
public Object deepClone() throws IOException, OptionalDataException,
ClassNotFoundException {// 将对象写到流里
ByteArrayOutoutStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);// 从流里读出来
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
}
3. 反射
public static void objCopy(Object src, Object dest) throws OpenapiBizException {
try {
Class c1 = src.getClass();
Class d1 = dest.getClass();
Field[] fields = c1.getDeclaredFields();
Field[] fields1 = d1.getDeclaredFields();
Set<String> set = new HashSet<String>();
for (Field temp : fields1) {
set.add(temp.getName());
}
for (Field field : fields) {
String name = field.getName();
if (!name.equals("serialVersionUID")) {
if (!set.contains(name)) {
continue;
}
String temp = Character.toUpperCase(name.charAt(0)) + name.substring(1);
Method m = c1.getMethod("get" + temp);
Method m1 = d1.getMethod("set" + temp, field.getType());
m1.invoke(dest, m.invoke(src, new Object[] {}));
}
}
} catch (Exception e) {
throw new OpenapiBizException(ErrorCodeEnum.ObjTransforError, e);
}
}