我们知道,一个实体类通过实现Cloneable接口可获得对象拷贝的能力。而拷贝又分为深拷贝和浅拷贝。很幸运,通过本文你可以了解到它们的区别。
首先,我们先来看这样一段代码:
@Data
public class Person implements Cloneable {
private String name;
private Person father;
public Person(String name) {
this.name = name;
}
public Person(String name, Person person) {
this.name = name;
this.father = person;
}
@Override
public Person clone() {
Person p = null;
try {
p = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
public static void main(String[] args) {
Person father = new Person("父亲");
Person son = new Person("大儿子", father);
Person person = (Person) son.clone();
person.setName("小儿子");
System.out.println("大儿子姓名:" + son.getName());
System.out.println("小儿子姓名:" + person.getName());
}
}
结果:
大儿子姓名:大儿子
小儿子姓名:小儿子
Process finished with exit code 0
突然有一天,他爹想让小儿子认个干爹,于是:
@Data
public class Person implements Cloneable {
private String name;
private Person father;
public Person(String name) {
this.name = name;
}
public Person(String name, Person person) {
this.name = name;
this.father = person;
}
@Override
public Person clone() {
Person p = null;
try {
p = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
public static void main(String[] args) {
Person father = new Person("父亲");
Person son = new Person("大儿子", father);
Person person = (Person) son.clone();
person.setName("小儿子");
System.out.println("大儿子姓名:" + son.getName());
System.out.println("小儿子姓名:" + person.getName());
person.getFather().setName("干爹");
System.out.println("大儿子父亲姓名:" + son.getFather().getName());
System.out.println("小儿子姓名:" + person.getFather().getName());
}
}
结果:
大儿子姓名:大儿子
小儿子姓名:小儿子
大儿子父亲姓名:干爹
小儿子姓名:干爹
Process finished with exit code 0
What? 他爹:我儿子呢?? 我俩儿都认干爹了??
什么原因呢?其实我们可以看到,此处的小儿子是通过大儿子拷贝过来的。我们知道所有类都继承自Object,Object提供了一个对象拷贝的默认方法,即上面代码中的son.clone方法,但是该方法是有缺陷的,它提供的是一种浅拷贝方式,也就是说它并不会把对象的所有属性全部拷贝一份,而是有选择性的拷贝,它的拷贝规则如下:
(1)基本类型如果变量是基本类型,则拷贝其值,比如int、float等。
(2)对象如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。这在Java中是很疯狂的,因为它突破了访问权限的定义:一个private修饰的变量,竟然可以被两个不同的实例对象访问,这让Java的访问权限体系情何以堪!
(3)String字符串这个比较特殊,拷贝的也是一个地址,是个引用,但是在修改时,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可以认为String是一个基本类型。
明白了这三个规则,上面的例子就很清晰了,小儿子对象是通过拷贝大儿子产生的,其父亲都是同一个人,也就是同一个对象,大儿子修改了父亲名称,小儿子也就跟着修改了—于是,父亲的两个儿子都没了!其实要更正也很简单,clone方法的代码如下:
@Override
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;
}
然后再运行,小儿子的父亲就不会是“干爹”了。如此就实现了对象的深拷贝(Deep Clone),保证拷贝出来的对象自成一体,不受“母体”的影响,和new生成的对象没有任何区别。
大儿子姓名:大儿子
小儿子姓名:小儿子
大儿子父亲姓名:父亲
小儿子姓名:干爹
Process finished with exit code 0