大家好,我是欧阳方超,微信公众号同名。
1 概述
在Java编程中,深拷贝和浅拷贝是对象复制的两种重要方式。理解这两种拷贝方式对于有效管理对象的状态和内存使用至关重要。本文将介绍深拷贝和浅拷贝的定义、实现方式。
2 对象复制的方式
2.1 浅拷贝
浅拷贝是基于原始对象另建一个新对象,新对象中非基本类型的成员变量跟原始对象中成员变量指向同一块内存空间。也就是说,对于包含非基本类型成员变量的对象,浅拷贝只会拷贝这些非基本类型成员变量的引用,而不会拷贝这些成员变量所指向的对象。
2.2 示例
public class ShadowCopyTest {
public static void main(String[] args) {
Address shanghai = new Address("shanghai");
Person originPerson = new Person("欧阳方超", shanghai);
Person copiedPerson = originPerson.shallowCopy();
Address address = originPerson.getAddress();
address.setCity("shenzhen");//通过引用改变对象的值,而不是通过set方法
System.out.println("originPerson: " + JSON.toJSONString(originPerson));
System.out.println("copiedPerson: " + JSON.toJSONString(copiedPerson));
}
}
class Person {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Person shallowCopy() {
return new Person(this.name, this.address);
}
}
class Address {
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
上面的代码中,Person类包含name和address两个属性,它们均为引用类型,当使用shallowCopy()方法进行浅拷贝时,新创建的Person对象的address属性和原始Person对象的address属性指向同一个Address对象。所以修改原始Person对象的address城市名称时,由于浅拷贝的特性,拷贝后的Person对象的address城市名称也会随之变化,因为它们都指向同一个Address对象。
上面代码运行结果显示,修改原始对象的city值,浅拷贝得到的对象city值也随之改变:
originPerson: {"address":{"city":"shenzhen"},"name":"欧阳方超"}
copiedPerson: {"address":{"city":"shenzhen"},"name":"欧阳方超"}
注意,Person类中name属性是String类型,也是引用类型,但是修改原始对象originPerson或拷贝对象copiedPerson的name值,均不会相互影响,应为String为不可变类。
2.3 深拷贝
深拷贝是基于已有对象创建一个新对象,并且在新对象中,所有非基本数据类型的成员变量,都从原始对象成员变量指向的对象进行复制,原始对象或新对象的修改互不影响。
2.4 示例
public class ShadowCopyTest {
public static void main(String[] args) {
Address shanghai = new Address("shanghai");
Person originPerson = new Person("欧阳方超", shanghai);
Person copiedPerson = originPerson.deepCopy();
Address address = originPerson.getAddress();
address.setCity("shenzhen");//通过引用改变对象的值,而不是通过set方法
System.out.println("originPerson: " + JSON.toJSONString(originPerson));
System.out.println("copiedPerson: " + JSON.toJSONString(copiedPerson));
}
}
class Person {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Person deepCopy() {
return new Person(this.name, this.address.clone());
}
}
class Address {
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public Address clone() {
return new Address(this.city);
}
}
在Person类中添加一个深拷贝方法deepCopy(),实现对Address对象的复制,Address类中重写clone()方法,实现city的深拷贝。
运行结果显示,修改原始对象的city值,不会影响拷贝对象的city值:
originPerson: {"address":{"city":"shenzhen"},"name":"欧阳方超"}
copiedPerson: {"address":{"city":"shanghai"},"name":"欧阳方超"}
3 clone()方法
上面的示例中,Address类已经使用了clone()方法,只不过这个是自定义的方法,我们要谈的是Object类中的clone(),该方法用于创建一个对象的副本,为了正确使用clone()方法,相应类需要实现java.lang.Clonable接口,这个接口是一个标记接口,本身没有任何方法,但它表示这个类运行被克隆。
当一个类没有对clone()方法进行特殊处理(即只是简单地调用super.clone()),并且类中包含引用类型的成员变量时,clone()方法会进行浅拷贝。因为Object类的clone()方法只是简单地按照内存布局来复制对象。对于基本数据类型,会直接复制其值;对于引用类型,只是复制引用(也就是对象的内存地址),而不会复制引用所指向的对象本身。
3.1 使用clone()方法进行深拷贝的挑战
复杂性:为了实现深拷贝,必须确保所有引用类型字段都实现了Cloneable接口,并在重写的clone()方法中调用它们的clone()方法。这在对象嵌套较深时会变得非常复杂和繁琐。
性能问题:clone()方法的实现可能会导致性能下降,尤其是在需要频繁进行深拷贝的情况下。由于每个引用类型都需要单独处理,可能会引入额外的开销。
可读性和维护性:使用clone()方法进行深拷贝可能会使代码变得难以理解和维护,特别是在涉及多个类和复杂对象结构时。
3.2 替代方案
构造函数:通过定义一个复制构造函数,可以更清晰地实现深拷贝。此方法允许在创建新对象时直接传递原对象的属性,从而避免了clone()的复杂性。
序列化:另一种实现深拷贝的方法是使用序列化。通过将对象序列化为字节流,然后再反序列化,可以创建一个完全独立的对象副本。这种方法在处理复杂对象时尤其有效。
4 结论
在Java中,选择使用深拷贝还是浅拷贝取决于具体需求。如果需要共享某些数据,可以使用浅拷贝;如果希望确保对象之间完全独立,则应使用深拷贝。理解这两种机制对于有效管理内存和避免意外的数据修改至关重要。
我是欧阳方超,把事情做好了自然就有兴趣了,如果你喜欢我的文章,欢迎点赞、转发、评论加关注。我们下次见。