前言
在很多人初学的时候,一定会被克隆是什么?怎么使用克隆?浅拷贝和深拷贝又是啥?所搞得晕头转向。这里我希望用最简单的语言向大家介绍我自己的理解。(我们马克思老师说,你能不能用农民也听得懂的语言介绍一下这个知识。)
克隆
首先我们需要介绍一下克隆(clone)。在介绍克隆之前,请回忆一下一个包含对象引用的变量建立副本时会发生什么。代码如下图所示:在下面的代码中,new Employee()构造了一个新的对象,并返回一个引用的值赋值给了original。 然后把original的值又赋值给了copy。换句话说,现在copy和original都引用了同一个对象。(关于这部分不懂的同学,可以看我之前的博文http://t.csdn.cn/H468S)
class Employee{
String name;
int age;
}
public class Test4 {
public static void main(String[] args) {
Employee original = new Employee();
Employee copy = original();
}
}
上述代码中,我们可以看到,original 和 copy 同时引用了Employee对象。当我们通过copy修改某个成员变量的值的时候,原本Employee对象中的值也会得到修改。但是!在日常生活中,我们希望copy一个新对象,希望通过引用修改新对象的值不会造成原对象的值发生变化。 因此在这种情况下,就要用到克隆方法(clone)了。也就是说,我们希望clone后的结果如下图所示。
克隆的实现
接下来的问题就是怎么在代码中实现这个克隆。有的老哥说,既然克隆有方法,我直接一勺三花淡奶,在对象后面一个点克隆不就完了。
显然这样的操作是不行滴。为啥呢? 在这里有一个前提,你要克隆某个类中的对象,这个类必须是可克隆的。那咋办?我们需要给Employee这个类实现一个Cloneable接口才行。如下图所示
( ′◔ ‸◔`)为啥还是不行?我们点进去这个Cloneable看看。
原来文件告诉我们,要实现这个克隆方法,我们必须重写Cloneable这个接口中的clone方法。结果如下图所示。(在main函数后面添加的throws CloneNotSupportedException友友们可以暂时不用理解,先加上就好)
ヾ(。`Д´。)但还是报错了,是什么情况?!注意看clone()方法的返回值是Object类,而object类是所有类父类。把Object类赋值给了Employee的copy引用发生了向下转型,因此这个时候必须要把Object类强制类型转换为Employee类。最终代码如下。
class Employee implements Cloneable {
String name;
int age;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test4 {
public static void main(String[] args) throws CloneNotSupportedException {
Employee original = new Employee();
Employee copy = (Employee) original.clone();
}
}
通过调试发现,现在original和copy引用了不同的Employee()对象,从而实现了我们期望实现的clone的需求。
浅拷贝
那么什么是浅拷贝呢?先说结论。clone默认实现的为浅拷贝。如果A拷贝了B,改变其中的一个对象的值,另一个对象的值会发生变化则是浅拷贝。而如果A拷贝了B,改变其中的一个对象的值,另一个对象的值不会发生任何变化则是深拷贝。
先看如下代码和结果
你可能会说:博主你骗人!这不是改变了一个对象的值,另一个不是没变化吗?这实现的不已经是深拷贝了吗?其实更加具体的浅拷贝说法是
如果 对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题、 但是如果对象包 含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。
因此对于浅拷贝来说,如果是数值或其他基本类型,那么浅拷贝一点毛病没有。但是如果引用了其他对象,那么浅拷贝就很危险了。看如下代码: 这里我新建了一个Money类,然后在Employee中新建了一个Money类的对象。
结果发现:改变了其中copy中m中money的值,original和copy居然都发生了变化!!这就是浅拷贝所带来的危险!!!!其中的内存布局图如下
可见copy虽然克隆了original的对象,但是其中m的对象的引用的值也被克隆了。最终导致两个克隆的对象,仍然共享了 new Money()这块空间。因此通过m引用修改money的值,当然会导致两个引用均发生变化。
浅拷贝的代码实现
class Money {
public double money = 100.2;
}
class Employee implements Cloneable {
String name;
int age;
public Money m = new Money();
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test4 {
public static void main(String[] args) throws CloneNotSupportedException {
Employee original = new Employee("abc",15);
Employee copy = (Employee) original.clone();
System.out.println(original.m.money);
System.out.println(copy.m.money);
System.out.println("================");
copy.m.money = 99;
System.out.println(original.m.money);
System.out.println(copy.m.money);
}
}
深拷贝
如上所说,而如果A拷贝了B,改变其中的一个对象的值,另一个对象的值不会发生任何变化则是深拷贝。
接上文所说,我希望的深拷贝后内存的布局应该如下所示。那么在代码层面如何实现呢?
深拷贝的实现
既然我们要克隆Money以实现深拷贝,那就让他实现Cloneable接口,并重写方法
当然还不够。 必须要修改Employee类中的clone方法,以实现employee对象和money对象的拷贝。因此代码如下:
深拷贝整体代码如下 :
class Money implements Cloneable{ //既然我们要克隆Money以实现深拷贝,那就让他实现Cloneable接口,并重写方法
public double money = 100.2;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Employee implements Cloneable {
String name;
int age;
public Money m = new Money();
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Employee employee = (Employee) super.clone(); //此代码只是克隆了Employee对象
employee.m = (Money) this.m.clone(); //此代码实现了Employee对象中Money对象的克隆。
return employee;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test4 {
public static void main(String[] args) throws CloneNotSupportedException {
Employee original = new Employee("abc",15);
Employee copy = (Employee) original.clone();
System.out.println(original.m.money);
System.out.println(copy.m.money);
System.out.println("================");
copy.m.money = 99;
System.out.println(original.m.money);
System.out.println(copy.m.money);
}
}
实验结果如下:
可以看到,我们修改一个对象的值,并不会影响另一个对象的值,这就完成了深拷贝了!!!