一 什么是克隆
克隆就是根据已有对象复制出另一个对象。比如用A克隆出B,一般在java中有如下约定:
// A与B的引用不同
A!=B
// A与B的类相同
A.getClass == B.getClass
// A和B内容相同
A.equals(B)
通常来说 A.equals(B) == true,但是这不是强制的要求,开发人员可根据具体需要决定是否重写equals方法。
二 怎么实现克隆
需要克隆的类 实现 Cloneable 接口,并重写其中的clone方法。其中super.clone()会把原始对象的属性克隆到新对象的属性中。
public class Student1 implements Cloneable{
private String name;
private int age;
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Student1 clone() throws CloneNotSupportedException {
return (Student1)super.clone();
}
}
测试程序如下:
public class TestClone {
public static void main(String[] args) {
Student1 xiaoming = new Student1(new String("小明"),12);
Student1 xiaoming_Copy = null;
try {
xiaoming_Copy = xiaoming.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(xiaoming.toString());
System.out.println(xiaoming_Copy.toString());
}
}
输出:
{“age”:12,”name”:”小明”}
{“age”:12,”name”:”小明”}
这样Student的克隆成功了。
实际上,Cloneable是个空接口,它没有定义任何成员。它只是一个对象可重写并使用clone方法的标志。
public interface Cloneable {
}
三 这样就结束了吗??
如果一个类中的域(属性)都是基本数据类型或者final类型当然就没有问题了,但是如果一个类的域是非final类型的引用类型(比如:数组、其他对象)。那么这样克隆就会出现一个问题,先看一个例子:
新加了一个Teacher类:
public class Teacher {
private String name;
private int age;
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
Stuent类中加入Teacher属性:
public class Student2 implements Cloneable {
private String name;
private int age;
private Teacher teacher;
public Student2(String name, int age, Teacher teacher) {
this.name = name;
this.age = age;
this.teacher = teacher;
}
@Override
protected Student2 clone() throws CloneNotSupportedException {
return (Student2)super.clone();
}
运行下面的测试程序:
public class TestClone {
public static void main(String[] args) {
Teacher teacher = new Teacher("王尼玛",41);
Student2 xiaoming = new Student2(new String("小明"),12,teacher);
Student2 xiaoming_Copy = null;
try {
xiaoming_Copy = xiaoming.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
xiaoming_Copy.getTeacher().setName("齐东强");
System.out.println(xiaoming.toString());
System.out.println(xiaoming_Copy.toString());
}
}
输出
{“age”:12,”name”:”小明”,”teacher”:{“age”:41,”name”:”齐东强”}}
{“age”:12,”name”:”小明”,”teacher”:{“age”:41,”name”:”齐东强”}}
发现拷贝对象副本在改变自己属性的时候会影响原始主本的数据,这是因为super.clone()只会把Student中对Teacher的引用值copy给克隆对象。所以主本和副本中的teacher对象指向了相同的堆地址。显然这不是我们想要的。
四 如何解决
嵌套多少层非final类型的引用,我们就拷贝多少层!
Teacher也 实现 Cloneable类,添加clone方法
public class Teacher implements Cloneable{
private String name;
private int age;
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Teacher clone() throws CloneNotSupportedException {
return (Teacher)super.clone();
}
}
修改Student中的clone方法为:
@Override
public Student2 clone() throws CloneNotSupportedException {
Student2 copy = (Student2)super.clone();
copy.teacher = teacher.clone();
return copy;
}
再次运行测试程序
{“age”:12,”name”:”小明”,”teacher”:{“age”:41,”name”:”王尼玛”}}
{“age”:12,”name”:”小明”,”teacher”:{“age”:41,”name”:”齐东强”}}
这次修改克隆对象的引用属性,原对象就不会被改变了
五 总结
其实三中的demo被称为浅克隆(浅拷贝),四中的解决方案为深克隆(深拷贝)
5.1 浅拷贝
浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的非final引用类型属性指向的对象
public Object clone() {
return super.clone();
}
直接使用super.clone()方法。
5.2 深拷贝
深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象
public Object clone() {
Object obj = super.clone();//先浅拷贝一下
//然后修正需要修正的属性
obj.属性1 = xxx;
obj.属性2 = xxx;
...
}
5.3 如何选择使用哪种拷贝方式
1.如果类中的属性只包含有基本数据类型或常量(包括String),那么这个类使用浅拷贝。
使用浅拷贝的类也可能需要修正,譬如代表序列号、其他唯一ID、对象的创建时间的属性,不管这些属性是基本类型还是常量,都需要修正。(常量需要修正时,不能使用super.clone())
2.如果类中的属性包含有非final的引用类型时(String不算),使用深拷贝,不然拷贝对象的改变会影响原对象,就失去了拷贝的意义。
5.4 什么时候使用克隆
- 当你想获取某个对象的数据内容,对其进行一系列操作又不想改变其内容时。
- 直接用构造器新生成一个对象的代价太大时。