(问题:深拷贝的实现方式,除了重写clone())
什么是浅拷贝和深拷贝
首先需要明白,浅拷贝和深拷贝都是针对一个已有对象的操作。那先来看看浅拷贝和深拷贝的概念。
在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 『 = 』号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。
而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
所以到现在,就应该了解了,所谓的浅拷贝和深拷贝,只是在拷贝对象的时候,对 类的实例对象 这种引用数据类型的不同操作而已。
总结来说:
1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
浅拷贝(clone)
先来看看浅拷贝的例子。
首先创建一个 class 为 FatherClass ,对其实现 Cloneable 接口,并且重写 clone()
方法。
然后先正常 new 一个 FatherClass 对象,再使用 clone() 方法创建一个新的对象。
最后看看输出的 Log :
I/cxmyDev: fatherA == fatherB : false I/cxmyDev: fatherA hash : 560973324 I/cxmyDev: fatherB hash : 560938740 I/cxmyDev: fatherA name : 张三 I/cxmyDev: fatherB name : 张三
可以看到,使用 clone()
方法,从 == 和 hashCode 的不同可以看出,clone()
方法实则是真的创建了一个新的对象。
但这只是一次浅拷贝的操作。(String类型的name就已经是浅拷贝了吧?)
来验证这一点,继续看下去,在 FatherClass 中,还有一个 ChildClass 的对象 child ,clone() 方法是否也可以正常复制它呢?改写一个上面的 Demo。
看到,这里将其内的 child 进行负责,用起来看看输出的 Log 效果。
I/cxmyDev: fatherA == fatherB : false I/cxmyDev: fatherA hash : 560975188 I/cxmyDev: fatherB hash : 560872384 I/cxmyDev: fatherA name : 张三 I/cxmyDev: fatherB name : 张三 I/cxmyDev: ================== I/cxmyDev: A.child == B.child : true I/cxmyDev: fatherA.child hash : 560891436 I/cxmyDev: fatherB.child hash : 560891436
从最后对 child 的输出可以看到,A 和 B 的 child 对象,实际上还是指向了统一个对象,只对对它的引用进行了传递。
深拷贝的实现
1. java Cloneable接口实现深拷贝
2. java 序列化实现深拷贝
3.反射
1. java Cloneable接口实现深拷贝
现在为了要在clone对象时进行深拷贝, 那么就要Clonable接口,覆盖并实现clone方法,除了调用父类中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来。
这种方式,需要类实现Colneable接口 clone 函数,在clone函数中调用super.clone。这种方式的深拷贝同样会带来另一个问题,如果类中有其他类的对象作为属性,则其他的类也需要实现Cloneable接口并重写clone()方法。然后在clone()方法中手动new一个对象类型的属性,将该对象clone后赋值给它。来一个例子,在下例中ComplexDO中包含了SimpleDO对象,要实现ComplexDO深拷贝,则需要先实现SimpleDO的clone接口:
public class SimpleDO implements Cloneable, Serializable { private int x = 1; private String s = "simpleDO"; @Override protected Object clone() throws CloneNotSupportedException { SimpleDO newClass = (SimpleDO)super.clone(); return newClass; } } public class ComplexDO implements Cloneable, Serializable { private int x = 1; private String s = "complex"; private Integer a = 123; private Integer b = 1234; private Integer c = 1334455; private String s2 = "hehehe"; private String s3 = "hahahaha"; private Long id = 1233245L; private ArrayList<SimpleDO> l = new ArrayList<SimpleDO>(); @Override public Object clone() throws CloneNotSupportedException { ComplexDO newClass = (ComplexDO) super.clone(); newClass.l = new ArrayList<SimpleDO>(); for (SimpleDO simple : this.l) { newClass.l.add((SimpleDO) simple.clone()); } return newClass; } }
结论:如果想要深拷贝一个对象, 这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份 , 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。
2. java 序列化实现深拷贝
这种方式的原理是利用java序列化,将一个对象序列化成二进制字节流,然后对该字节流反序列化赋值给一个对象。代码示例:
public Object seirCopy(Object src) { try { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(src); ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); ObjectInputStream in = new ObjectInputStream(byteIn); Object dest = in.readObject(); return dest; } catch (Exception e) { //do some error handler return null; } }
当然,也可以选用json等序列化的库来完成序列化,这种方式有效的规避了Cloneabel接口的可扩展缺点,一个函数就可以基本上适用于所有的类.缺点是相对内存拷贝,序列化需要先将对象转换成二进制字节流,然后反序列化将该二进制字节流重新拷贝到一块对象内存,相对慢点。
3.反射
通过反射进行,通过反射,获取对象的内部数据域,然后将数据依次复制