一个类实现Cloneable接口,那么它就具有了被拷贝的能力,如覆写clone方法之后就完全具备拷贝的能力。因为拷贝是在内存中进行的,所以在性能上比new生成对象要快很多,那么,拷贝是完全拷贝吗?下面看一个例子。
public class Person implements Cloneable{
//姓名
private String name;
//父亲
private Person father;
public Person(String name) {
super();
this.name = name;
}
public Person(String name, Person father) {
super();
this.name = name;
this.father = father;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getFather() {
return father;
}
public void setFather(Person father) {
this.father = father;
}
//实现拷贝
@Override
public Person clone(){
Person p=null;
try {
p=(Person)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
在这边我们定义了一个Person类,现在要描述一个场景,一个父亲有两个儿子,小儿子通过拷贝大儿子对象来生成,下面看下测试的方法。
这边可以看到输出的没有问题,那么突然有一天,父亲想让大儿子去认一个义父,也就是图上注释掉的代码,将代码写到小儿子命名完后。那么结果会如何呢?
可以看到小儿子的父亲也变了!这是为什么呢?因为拷贝时super.clone()方法调用的是Object的方法,就是这个方法的原因,它是一种浅拷贝的方式。也就是说不是把对象的所有属性拷贝一份,而是有选择性的拷贝。拷贝的规则如下:
-
基本类型
如果变量是基本类型,则拷贝值。
-
对象
如果对象是一个实例遍历,则拷贝地址引用,也就是说拷贝出来新的对象与原有的对象共享该实例变量。
-
String字符串
这个比较特殊,拷贝的是一个地址,是个引用,但是在修改的时候,他会在字符串池中重新生成新的字符串,原有的字符串对象保持不变。
所以也就是说,小儿子的对象是通过拷贝大儿子产生的,父亲是同一个,也就是同一个对象。于是修改了大儿子的父亲值,小儿子也跟着修改了。同理改小儿子的父亲也是一样,大儿子也会跟着修改。
那么怎么改呢?就是在拷贝的时候生成新的对象就可以了。
public Person clone(){
Person p=null;
try {
p=(Person)super.clone();
p.setFather(new Person(p.getFather().getName()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}