引用拷贝
在介绍浅拷贝与深拷贝之前,让我们区分一下引用拷贝和对象拷贝。顾名思义,引用拷贝会创建指向对象的引用变量的副本。
如果我们有一个 School1 对象,并且有一个指向它的school变量,并且我们创建了一个引用副本,那么我们现在将有两个School1变量,但仍然是一个对象。
class School1{
public String name;
public String address;
public School1(String name, String address) {
this.name = name;
this.address = address;
}
}
public class ReferenceCopyExample {
public static void main(String[] args) {
School1 school = new School1("北京大学", "北京");
School1 otherSchool = school;
System.out.println("[1]:" + school);
System.out.println("[2]:" + otherSchool);
}
}
//输出
[1]:copy.School1@1f17ae12
[2]:copy.School1@1f17ae12
由输出结果可以看出,school 和otherSchool中的地址值是相同的,那么它们肯定是指向了一个相同的对象(“北京大学”, “北京”),并没有创建新的School对象。这就叫做引用拷贝,内存图如下:
引用拷贝不属于对象拷贝,因为它并不会拷贝新的对象,下面介绍的浅拷贝和深拷贝则属于对象拷贝,在拷贝的过程中会实实在在的创建新对象。
对象拷贝
对象拷贝会创建对象本身的副本,为什么要复制一个对象呢?如果我们想修改或移动一个对象,同时仍然保留原始对象,就需要创建一个对象副本(也称为克隆或复制)。
对象拷贝又分为两种:浅拷贝和深拷贝。
浅拷贝
先介绍一点铺垫知识:Java中的数据类型分为基本数据类型和引用数据类型。
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象对应的成员变量。也就是说两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
也就是说对象的浅拷贝会复制“主”对象,但不复制内部对象。“内部对象”在原始对象及其副本之间共享。
通过重写clone()方法可以实现浅拷贝,Object类是类结构的根类,其中有一个方法为clone(),这个方法就是进行的浅拷贝。我们可以通过调用clone()方法来实现对象的浅拷贝。
但是需要注意:Object类的clone()是受保护的(被protected修饰),所以我们无法直接使用。我们的解决方法是:使用clone方法的类实现Cloneable接口,然后重写clone()方法,通过super.clone()调用Object类中的原clone方法。如果还是不太理解,没关系,相信下面的例子会帮助你理解浅拷贝这块的内容。
class Address{
public String city;
public Address(String city) {
this.city = city;
}
}
//1.实现Cloneable接口
class School2 implements Cloneable{
public String name;
public int age;
public Address address;
public School2(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
//重写clone方法,方法中通过super.clone()调用Object类中的原clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//浅拷贝测试
public class ShallowCopyExample {
public static void main(String[] args) throws CloneNotSupportedException {
School2 school = new School2("北京大学", 11, new Address("北京"));
School2 otherSchool = (School2)school.clone();
System.out.println("[1]:" + school);
System.out.println("[2]:" + otherSchool);
System.out.println();
System.out.println("[3]:" + school.address);
System.out.println("[4]:" + otherSchool.address);
System.out.println();
school.name = "上海交大";
school.age = 66;
school.address.city = "上海";
System.out.println("[5]:" + otherSchool.name);
System.out.println("[6]:" + otherSchool.age);
System.out.println("[7]:" + otherSchool.address.city);
}
}
//输出
[1]:copy.School3@4d405ef7
[2]:copy.School3@6193b845
[3]:copy.Address@2e817b38
[4]:copy.Address@2e817b38
[5]:北京大学
[6]:11
[7]:上海
现在来分析程序的运行结果:通过[1]和[2]的输出可知:对象school和otherSchool的地址不一样,也就是说浅拷贝复制出一份新的对象。
通过[3]和[4]的输出可知:school和otherSchool中的的引用类型变量address地址一样,说明浅拷贝只是将school中的成员address的引用值(内存地址)复制一份给otherSchool中的address,并不会为引用类型成员变量创建新的对象。
通过[5]、[6]和[7]的输出可知:对于基本类型和String类型的成员变量,修改源对象的该变量,不会影响拷贝后的对象值;对于引用类型的成员变量address,修改school的该成员变量值,拷贝后的对象otherSchool中该成员也变了,因为school和otherSchool中的address指向的是同一个Adrress对象。我们可以看对应的内存布局来理解:
浅拷贝之后,修改school中成员变量之前内存布局:
修改school中成员变量之后内存布局:
深拷贝
与浅拷贝对应的就是深拷贝了,其不同于浅拷贝的是:对于对象中引用类型的成员,在拷贝的时候也是实实在在创建了一份。深拷贝相比于浅拷贝速度较慢并且花销较大。简而言之,深拷贝把要复制的对象所引用的对象也都复制了一遍。
class Address1 implements Cloneable{
public String city;
public Address1(String city) {
this.city = city;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//2.School3实现Cloneable接口
class School3 implements Cloneable{
public int age;
public Address1 address;
public School3(int age, Address1 address) {
this.age = age;
this.address = address;
}
public Address1 getAddress() {
return address;
}
public void setAddress(Address1 address) {
this.address = address;
}
//重写clone方法,方法中通过super.clone()调用Object类中的原clone方法
@Override
public Object clone() throws CloneNotSupportedException {
School3 school3 = (School3)super.clone();
school3.setAddress((Address1) school3.getAddress().clone());
return school3;
}
}
//深拷贝测试
public class DeepCopyExample {
public static void main(String[] args) throws CloneNotSupportedException {
School3 school = new School3(11, new Address1("北京"));
School3 otherSchool = (School3)school.clone();
System.out.println("[1]:" + school);
System.out.println("[2]:" + otherSchool);
System.out.println();
System.out.println("[3]:" + school.address);
System.out.println("[4]:" + otherSchool.address);
System.out.println();
school.age = 66;
school.address.city = "上海";
System.out.println("[5]:" + otherSchool.age);
System.out.println("[6]:" + otherSchool.address.city);
}
}
//输出
[1]:copy.School3@4d405ef7
[2]:copy.School3@6193b845
[3]:copy.Address1@2e817b38
[4]:copy.Address1@c4437c4
[5]:11
[6]:北京
现在来分析程序的运行结果:
通过[1]和[2]的输出可知:源对象和拷贝的对象 地址不一样,也就是说深拷贝复制出一份新的对象,这点和浅拷贝一样。通过[3]和[4]的输出可知:源对象和拷贝的对象中的的引用类型变量address地址也一样,说明深拷贝将引用类型的成员变量对象也 拷贝了一份,这是不同于浅拷贝的地方
。通过[5]和[6]的输出可知:school和otherSchool中的两个Address1类型引用指向的是两个对象,所以对school中的Address1对象修改不影响otherSchool对象中的Address1对象。
下面这个内存布局可以帮助我们理解。
深拷贝之后,修改school中成员变量之前内存布局:
修改school中成员变量之后内存布局: