Java中的深拷贝与浅拷贝
什么是拷贝
在Java中,拷贝是指创建一个对象的副本。拷贝主要分为两种类型:浅拷贝(Shallow Copy)和深拷贝(Deep Copy)。理解这两种拷贝的区别对于编写正确的Java程序非常重要,特别是在处理对象引用时。
浅拷贝(Shallow Copy)
浅拷贝是指创建一个新对象,然后将原始对象的所有字段值复制到新对象中。如果字段是基本类型,则直接复制其值;如果字段是引用类型,则复制引用但不复制引用的对象。因此,原始对象和副本对象将共享引用类型的字段。
浅拷贝的实现方式
在Java中,实现浅拷贝有几种方式:
-
使用clone()方法:Object类提供了clone()方法,可以实现浅拷贝
-
使用构造方法:通过构造方法复制每个字段
-
使用拷贝工厂方法
class Department implements Cloneable {private String name;public Department(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}class Employee implements Cloneable {private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}public String getName() {return name;}public Department getDepartment() {return department;}public void setName(String name) {this.name = name;}public void setDepartment(Department department) {this.department = department;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}public class ShallowCopyExample {public static void main(String[] args) throws CloneNotSupportedException {Department department = new Department("IT");Employee original = new Employee("John", department);Employee cloned = (Employee) original.clone();System.out.println("Original: " + original.getName() + " - " + original.getDepartment().getName());System.out.println("Cloned: " + cloned.getName() + " - " + cloned.getDepartment().getName());// 修改克隆对象的departmentcloned.getDepartment().setName("HR");System.out.println("\nAfter modifying cloned object:");System.out.println("Original: " + original.getName() + " - " + original.getDepartment().getName());System.out.println("Cloned: " + cloned.getName() + " - " + cloned.getDepartment().getName());}
}
输出结果:
Original: John - IT
Cloned: John - ITAfter modifying cloned object:
Original: John - HR
Cloned: John - HR
可以看到,修改克隆对象的department后,原始对象的department也被修改了,这是因为它们引用的是同一个Department对象。
深拷贝(Deep Copy)
深拷贝是指创建一个新对象,然后递归地复制原始对象的所有字段。对于引用类型的字段,不仅复制引用,还会创建引用对象的新副本。因此,原始对象和副本对象完全独立,互不影响。
深拷贝的实现方式
实现深拷贝有几种常见方法:
-
重写clone()方法:在clone()方法中手动复制所有引用类型的字段
-
使用序列化:将对象序列化为字节流,然后再反序列化为新对象
-
使用拷贝构造方法:创建接受原对象并复制所有字段的构造方法
-
使用第三方库:如Apache Commons Lang的SerializationUtils
方法1:重写clone()方法
class Department implements Cloneable {private String name;public Department(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}class Employee implements Cloneable {private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}public String getName() {return name;}public Department getDepartment() {return department;}public void setName(String name) {this.name = name;}public void setDepartment(Department department) {this.department = department;}@Overrideprotected Object clone() throws CloneNotSupportedException {Employee cloned = (Employee) super.clone();cloned.department = (Department) department.clone(); // 深度拷贝departmentreturn cloned;}
}public class DeepCopyExample1 {public static void main(String[] args) throws CloneNotSupportedException {Department department = new Department("IT");Employee original = new Employee("John", department);Employee cloned = (Employee) original.clone();System.out.println("Original: " + original.getName() + " - " + original.getDepartment().getName());System.out.println("Cloned: " + cloned.getName() + " - " + cloned.getDepartment().getName());// 修改克隆对象的departmentcloned.getDepartment().setName("HR");System.out.println("\nAfter modifying cloned object:");System.out.println("Original: " + original.getName() + " - " + original.getDepartment().getName());System.out.println("Cloned: " + cloned.getName() + " - " + cloned.getDepartment().getName());}
}
输出结果:
Original: John - IT
Cloned: John - ITAfter modifying cloned object:
Original: John - IT
Cloned: John - HR
方法2:使用序列化实现深拷贝
import java.io.*;class Department implements Serializable {private String name;public Department(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}class Employee implements Serializable {private String name;private Department department;public Employee(String name, Department department) {this.name = name;this.department = department;}public String getName() {return name;}public Department getDepartment() {return department;}public void setName(String name) {this.name = name;}public void setDepartment(Department department) {this.department = department;}public Employee deepCopy() throws IOException, ClassNotFoundException {// 将对象写入字节数组ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);oos.flush();// 从字节数组读取对象ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Employee) ois.readObject();}
}public class DeepCopyExample2 {public static void main(String[] args) throws IOException, ClassNotFoundException {Department department = new Department("IT");Employee original = new Employee("John", department);Employee cloned = original.deepCopy();System.out.println("Original: " + original.getName() + " - " + original.getDepartment().getName());System.out.println("Cloned: " + cloned.getName() + " - " + cloned.getDepartment().getName());// 修改克隆对象的departmentcloned.getDepartment().setName("HR");System.out.println("\nAfter modifying cloned object:");System.out.println("Original: " + original.getName() + " - " + original.getDepartment().getName());System.out.println("Cloned: " + cloned.getName() + " - " + cloned.getDepartment().getName());}
}
输出结果与上一个相同:
Original: John - IT
Cloned: John - ITAfter modifying cloned object:
Original: John - IT
Cloned: John - HR
浅拷贝与深拷贝的比较
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
基本类型字段 | 复制值 | 复制值 |
引用类型字段 | 复制引用(共享对象) | 递归复制对象(不共享对象) |
实现复杂度 | 简单(通常只需调用super.clone) | 复杂(需要处理所有引用类型字段) |
性能 | 较高(复制较少) | 较低(需要复制更多对象) |
对象独立性 | 低(引用类型字段会相互影响) | 高(完全独立) |
如何选择拷贝方式
选择浅拷贝还是深拷贝取决于具体需求:
-
使用浅拷贝的情况:
-
对象的所有字段都是基本类型
-
引用类型字段是不可变的(如String)
-
你确实需要共享某些对象的状态
-
-
使用深拷贝的情况:
-
对象包含可变引用类型字段
-
你需要完全独立的副本
-
副本的修改不应影响原始对象
-
注意事项
-
clone()方法的限制:
-
需要实现Cloneable接口,否则会抛出CloneNotSupportedException
-
clone()方法是protected的,如果要在其他包中调用,需要重写为public
-
深拷贝实现需要确保所有相关类都正确实现了clone()
-
-
序列化的限制:
-
所有相关类都必须实现Serializable接口
-
序列化性能较低,不适合频繁拷贝的场景
-
某些特殊对象(如线程)无法通过序列化正确拷贝
-
-
循环引用问题:
-
在深拷贝时,如果对象图中有循环引用,需要特别处理,否则可能导致栈溢出
-
最佳实践
1.考虑使用拷贝构造方法或拷贝工厂方法替代clone():
public Employee(Employee other) {this.name = other.name;this.department = new Department(other.department);
}
2.对于复杂的对象图,考虑使用第三方库如Apache Commons Lang的SerializationUtils.clone():
Employee cloned = SerializationUtils.clone(original);
3.明确文档化你的拷贝行为,让使用者知道是浅拷贝还是深拷贝。