之前在学Javase时没注意到克隆这个方法,后面再网上看博客和面试题是经常看到克隆、深拷贝和浅拷贝这些词语,刚开始看被吓一跳,以为有多复杂,今天下决心给他搞明白,突然发现原来这么简单,给以后的自己一个警醒,往往认为难的其实也没什么,一定要克服心理关。
首先理解克隆:
我们正常复制一个引用类型的数据时,会直接用赋值符号进行赋值,而这种赋值方法其实俩者共用的还是同一个对象,通过其中一个来改变对象中的值,另一个也相应的发生改变。
而克隆则是,完全创造出一个新的对象,有自己的新的地址,只不过初始信息是和原对象是一样的,而克隆实现的方法有俩钟,先来看第一种,Object类中的Clone方法。
public class testone implements Cloneable{
public int i=10;
public Object clone() {
testone to=null;
try {
to=(testone) super.clone();
//调用Object方法进行克隆
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return to;
}
}
//这是重写Object中的Clone方法
public class testtwo {
public static void main(String[] args) {
testone to=new testone();
testone to1=(testone) to.clone();
System.out.println(to.i);
System.out.println(to1.i);
System.out.println("to的地址"+to);
System.out.println("to1的地址"+to1);
System.out.println(to==to1);
}
}
结果:
10
10
to的地址com.xp.testone@15db9742
to1的地址com.xp.testone@6d06d69c
false
可以看到俩个对象的地址不一样,但是里面的内容却是一样的。
克隆中要注意的几点是:
首先克隆对象必须实现Cloneable这个接口,但是其实这个借口没有任何的方法,它只是标记了这个对象是可以克隆的而已,类似于序列化的接口,但是千万不要以为有没有这个接口都一样,当没有此接口时,克隆会出错。
java.lang.CloneNotSupportedException: com.xp.testone
at java.lang.Object.clone(Native Method)
at com.xp.testone.clone(testone.java:11)
at com.xp.testtwo.main(testtwo.java:7)
Exception in thread "main" java.lang.NullPointerException
at com.xp.testtwo.main(testtwo.java:9)
10
第二点:克隆的底层是利用Object的Clone方法进行实现的,而Object的Clone方法为native方法,是由C实现的
protected native Object clone() throws CloneNotSupportedException;
克隆就是这些。
接下来看深复制(深拷贝)、浅复制(浅拷贝),这些东西
先假设有三个类,A B C,B中有A的引用,C中又有B的引用。
浅复制就是
那么浅复制就是当复制c时,c中的值确实是发生了克隆,但是c中的B却没有,b种的A也没有发生克隆,这俩个都是简单的引用复制而已。
看代码:
public class people {
public String s="hello world";
}
public class qianClone implements Cloneable{
String name;
public people p;
public qianClone(String name,people p) {
this.name=name;
this.p=p;
}
public Object qianClone() {
qianClone qc=null;
try {
qc=(qianClone) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return qc;
}
}
public static void main(String[] args) {
people p=new people();
qianClone qc=new qianClone("tom",p);
qianClone qcClone=(qianClone) qc.qianClone();
qcClone.name="lisa";
qcClone.p.s="bye bye";
System.out.println(qc);
System.out.println(qcClone);
System.out.println(qc.name);
System.out.println(qcClone.name);
System.out.println(qc.p.s);
System.out.println(qc.p.s);
}
com.xp.qianClone@15db9742
com.xp.qianClone@6d06d69c
tom
lisa
bye bye
bye bye
图中有俩个对象:people和qianClone,其中qianClone中有people的引用,当改变qianClone的克隆对象中的值时原对象没有变,说明这俩者确实发生了克隆,但是当改变其中一个中的people引用时,另外一个1也跟着发生改变,说明这是发生了引用的简单复制,并没有发生克隆,这就是浅复制。
那么深复制又是怎么样的呢?
深复制就是在克隆一个对象时,其内的引用也发生了相应的克隆,都会产生完全没有关联的对象。
看代码:
public class people implements Cloneable{
public String s="hello world";
public Object peopleClone() {
people p=null;
try {
p=(people) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
public class deepClone implements Cloneable{
String name;
public people p;
public deepClone(String name,people p) {
this.name=name;
this.p=p;
}
public Object qianClone() {
deepClone dc=null;
try {
dc=(deepClone) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
dc.p=(people) p.peopleClone();
//对内部引用p在进行一次拷贝
return dc;
}
}
public static void main(String[] args) {
people p=new people();
deepClone dc=new deepClone("tom",p);
deepClone dcClone=(deepClone) dc.qianClone();
dcClone.name="lisa";
dcClone.p.s="bye bye";
System.out.println(dc);
System.out.println(dcClone);
System.out.println(dc.name);
System.out.println(dcClone.name);
System.out.println(dc.p.s);
System.out.println(dc.p.s);
}
com.xp.deepClone@15db9742
com.xp.deepClone@6d06d69c
tom
lisa
hello world
hello world
其实和浅复制是一样的道理,只不过它在people中也重写了Clone方法,在深复制时将people也进行了一次深复制。
以上是实现Cloneable采用Object的Clone方法来实现的
接下来看第二种实现克隆的方法:
采用序列化直接将对象通过流的形式复制一份
这种方式没有浅复制和深复制之分,直接就是深复制。
看代码:
public class people implements Serializable{
public String s="hello world";
public Object peopleClone() {
people p=null;
try {
p=(people) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
public class deepClone implements Serializable {
String name;
public people p;
public deepClone(String name,people p) {
this.name=name;
this.p=p;
}
public Object deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//这一步是创建对象输出流,将本对象从内存中输出到字节流中
ByteArrayInputStream bs = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream os = new ObjectInputStream(bs);
return os.readObject();
//这一步是将字节流中的对象通过输入字节流输入到Object(方法返回值)类中
}
}
public static void main(String[] args) throws ClassNotFoundException, IOException {
people p=new people();
deepClone dc=new deepClone("tom",p);
deepClone dc1=(deepClone) dc.deepClone();
//这一步就完成了克隆,接下来就改变值来验证
dc1.name="lisa";
dc1.p.s="bye bye";
System.out.println(dc);
System.out.println(dc1);
System.out.println(dc.name);
System.out.println(dc1.name);
System.out.println(dc.p.s);
System.out.println(dc1.p.s);
}
com.xp.deepClone@55f96302
com.xp.deepClone@3b07d329
tom
lisa
hello world
bye bye
在实现这个方法时,我直接就使用对象里面有其他的对象引用来做验证,发现这种方法直接就是深复制,但是看大神博客说是比较耗时。
感受:真的得做验证,一些知识光看会发现很复杂,但是一验证就发现很简单,思路也会很清晰的。