Java浅拷贝与深拷贝
浅拷贝
浅拷贝是按位拷贝对象,在进行浅拷贝时会创建一个新对象。如果被拷贝对象的属性是基本类型,那么拷贝的就是基本类型的值;如果属性是对象(内存地址/引用类型),拷贝的就是对象 。属性是对象时,其中一个对象改变了这个地址,就会影响到另一个对象。
我们先写一个被拷贝对象。
/**
* 对象(内存地址)
* 作为一个对象的属性
*/
public class BaseInfo {
private String name;
private int age;
public BaseInfo(String name, int age) {
setName(name);
setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class People implements Cloneable {
private BaseInfo baseInfo;
private String email;
public People(BaseInfo baseInfo, String email) {
setBaseInfo(baseInfo);
setEmail(email);
}
public BaseInfo getBaseInfo() {
return baseInfo;
}
public void setBaseInfo(BaseInfo baseInfo) {
this.baseInfo = baseInfo;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void resetPeopleInfo(String name,int age,String email){
baseInfo.setName(name);
baseInfo.setAge(age);
setEmail(email);
}
public String getPeopleInfo() {
return "name = " + getBaseInfo().getName() + ",age = " + getBaseInfo().getAge() + ",email = " + getEmail();
}
}
为了实现被拷贝的能力,需要将被拷贝的对象实现Cloneable接口,并覆写clone()方法。
拷贝一下:
BaseInfo baseInfo = new BaseInfo("张三", 18);
People people_1 = new People(baseInfo, "aaa@qq.com");
Log.d(TAG, "people_1---> " + people_1.toString());
Log.d(TAG, "people_1的baseInfo---> " + people_1.getBaseInfo().toString());
Log.d(TAG, "people_1---> " + people_1.getPeopleInfo());
try {
People people_2 = (People) people_1.clone();
Log.d(TAG,"-------------------------------------------");
Log.d(TAG, "people_2---> " + people_2.toString());
Log.d(TAG, "people_2的baseInfo---> " + people_2.getBaseInfo().toString());
Log.d(TAG, "people_2---> " + people_2.getPeopleInfo());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
再看下日志:
02-06 02:56:16.392 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_1---> copy.People@94b6efe
02-06 02:56:16.392 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_1的baseInfo---> copy.BaseInfo@763d65f
02-06 02:56:16.393 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_1---> name = 张三,age = 18,email = zhangsan@163.com
02-06 02:56:16.393 5943-5943/com.example.lenovo.testdemo D/CopyActivity: -------------------------------------------
02-06 02:56:16.393 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_2---> copy.People@d4d68ac
02-06 02:56:16.394 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_2的baseInfo---> copy.BaseInfo@763d65f
02-06 02:56:16.394 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_2---> name = 张三,age = 18,email = zhangsan@163.com
可以看到,两个People对象的地址是不一样的,即people_2是新生成的一个地址,然后people_1的属性赋值给了people_2。再仔细看下,就能发现两个People对象的BaseInfo属性却是一个地址。就说明,拷贝的时候对于对象类型的属性,拷贝的只是地址,没有为这样非基本类型数据开辟新的地址空间。
当我们重置一下people_1对象的属性时,可以看一下people_2对象的属性的值的变化。
people_1.resetPeopleInfo("李四",37,"aaa@qq.com");
Log.d(TAG, "people_1---> " + people_1.toString());
Log.d(TAG, "people_1的baseInfo---> " + people_1.getBaseInfo().toString());
Log.d(TAG, "people_1---> " + people_1.getPeopleInfo());
Log.d(TAG,"-------------------------------------------");
Log.d(TAG, "people_2---> " + people_2.toString());
Log.d(TAG, "people_2的baseInfo---> " + people_2.getBaseInfo().toString());
Log.d(TAG, "people_2---> " + people_2.getPeopleInfo());
我们改变下people_1的属性:name由张三改成李四,年龄由18改成37,邮件由aaa@qq.com改成aaa@qq.com
看下数据的变化情况
02-06 02:56:16.396 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_1---> copy.People@94b6efe
02-06 02:56:16.396 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_1的baseInfo---> copy.BaseInfo@763d65f
02-06 02:56:16.396 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_1---> name = 李四,age = 37,email = lisi@126.com
02-06 02:56:16.396 5943-5943/com.example.lenovo.testdemo D/CopyActivity: -------------------------------------------
02-06 02:56:16.396 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_2---> copy.People@d4d68ac
02-06 02:56:16.397 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_2的baseInfo---> copy.BaseInfo@763d65f
02-06 02:56:16.397 5943-5943/com.example.lenovo.testdemo D/CopyActivity: people_2---> name = 李四,age = 37,email = zhangsan@163.com
最终的结果是people_1的三个属性都变过来了,但是people_2的三个属性中作为基本数据类型的email还是原来的aaa@qq.com,引用数据类型变了过来。
这就印证了浅拷贝的特点:
1)新开辟内存地址,生成一个新的对象;
2)基本数据类型独立开来,不随着原来对象属性的变化而变化;
3)引用数据类型是公用的,随着一个变化,另一个也会跟着变化。
深拷贝
深拷贝会拷贝所有的属性(基本数据类型和引用数据类型),并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。相对于浅拷贝,深拷贝速度较慢,花销也大一些。
现在为了要在clone对象时进行深拷贝, 那么就要Clonable接口,覆盖并实现clone方法,除了调用父类中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来。由上述的例子可以看到,只是用Object中默认的clone方法也是浅拷贝。
为了实现深拷贝,首先需要将被拷贝对象的引用类型的属性也要实现Cloneable接口,并覆写clone() 方法。
public class BaseInfo implements Cloneable {
private String name;
private int age;
public BaseInfo(String name, int age) {
setName(name);
setAge(age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
然后覆写被拷贝对象的clone()方法,将默认的clone()方法进行改变,在这个方法中引用对象类型进行拷贝,具体的拷贝如下。
@Override
protected Object clone() throws CloneNotSupportedException {
People newPeople = (People) super.clone();
newPeople.setBaseInfo((BaseInfo) baseInfo.clone());
return newPeople;
}
这样操作的话,就会在拷贝时重新为引用对象的属性重新生成一个地址空间并赋值。
具体的日志信息如下:
02-06 03:20:16.366 24786-24786/com.example.lenovo.testdemo D/CopyActivity: people_1---> copy.People@94b6efe
02-06 03:20:16.366 24786-24786/com.example.lenovo.testdemo D/CopyActivity: people_1的baseInfo---> copy.BaseInfo@763d65f
02-06 03:20:16.366 24786-24786/com.example.lenovo.testdemo D/CopyActivity: people_1---> name = 李四,age = 37,email = lisi@126.com
02-06 03:20:16.366 24786-24786/com.example.lenovo.testdemo D/CopyActivity: -------------------------------------------
02-06 03:20:16.367 24786-24786/com.example.lenovo.testdemo D/CopyActivity: people_2---> copy.People@d4d68ac
02-06 03:20:16.367 24786-24786/com.example.lenovo.testdemo D/CopyActivity: people_2的baseInfo---> copy.BaseInfo@e22975
02-06 03:20:16.368 24786-24786/com.example.lenovo.testdemo D/CopyActivity: people_2---> name = 张三,age = 18,email = zhangsan@163.com
可以看到,深拷贝时如果改变了people_1的引用对象类型时并没有改变people_2的引用对象类型的属性值。