在原型模式(Prototype Pattern)中,深拷贝(Deep Copy) 和浅拷贝(Shallow Copy) 是实现对象复制时的两种核心方式,它们的区别在于对对象中引用类型成员的处理方式不同。
1. 浅拷贝(Shallow Copy)
浅拷贝会创建一个新对象,然后复制原对象中所有的基本数据类型成员 (如 int、float、string 等),但对于引用类型成员(如对象、数组、集合等),仅复制其引用地址,而非引用指向的实际对象。
- 特点:新对象和原对象的引用类型成员共享同一块内存空间,修改其中一个会影响另一个。
- 适用场景:对象中仅包含基本数据类型,或无需独立修改引用类型成员时。
2. 深拷贝(Deep Copy)
深拷贝会创建一个新对象,不仅复制原对象的基本数据类型成员,还会递归复制所有引用类型成员所指向的实际对象,即新对象的引用类型成员与原对象的引用类型成员完全独立,占用不同的内存空间。
- 特点:新对象和原对象完全独立,修改其中一个的引用类型成员不会影响另一个。
- 适用场景:对象中包含引用类型成员,且需要复制后独立修改时。
举例说明
假设存在一个Person类,包含基本类型name和引用类型address(Address类的实例):
java
class Address {
String city;
// 构造函数、getter、setter...
}
class Person {
String name; // 基本类型
Address address; // 引用类型
// 构造函数、getter、setter...
}
浅拷贝 :复制Person时,name会被复制为新值,但address仅复制引用,新Person和原Person的address指向同一个Address对象。
java
// 浅拷贝实现(仅示例,实际可通过Cloneable接口)
Person shallowCopy(Person original) {
Person copy = new Person();
copy.name = original.name; // 复制基本类型
copy.address = original.address; // 复制引用(共享对象)
return copy;
}
深拷贝 :复制Person时,不仅复制name,还会创建一个新的Address对象,复制原address的内容,使新Person的address完全独立。
java
// 深拷贝实现
Person deepCopy(Person original) {
Person copy = new Person();
copy.name = original.name; // 复制基本类型
// 复制引用类型的实际对象
copy.address = new Address();
copy.address.city = original.address.city;
return copy;
}
总结
| 类型 | 复制内容 | 独立性 | 实现复杂度 |
|---|---|---|---|
| 浅拷贝 | 基本类型 + 引用类型的地址 | 引用类型成员不独立 | 简单 |
| 深拷贝 | 基本类型 + 引用类型的实际对象 | 完全独立 | 较复杂(需递归复制) |
在原型模式中,选择深拷贝还是浅拷贝,取决于业务是否需要复制后的对象与原对象完全隔离。如果对象结构简单且无需独立修改引用成员,浅拷贝更高效;若需完全独立,则必须使用深拷贝。
1. 基础类定义(被拷贝的对象结构)
先定义两个类:Address(引用类型)和 Person(包含基本类型和引用类型成员)。
java
// 地址类(引用类型)
class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
// getter和setter
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
// 重写toString方便打印
@Override
public String toString() {
return "Address{city='" + city + "', street='" + street + "'}";
}
}
// 人类(需要被拷贝的原型类)
class Person implements Cloneable { // 实现Cloneable接口标记可拷贝
private String name; // 基本类型(String本质是引用类型,但Java中String不可变,表现类似基本类型)
private int age; // 基本类型
private Address address; // 引用类型(Address对象)
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// getter和setter
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 Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
// 浅拷贝实现(重写clone方法)
@Override
protected Object clone() throws CloneNotSupportedException {
// 调用父类clone方法,仅复制基本类型和引用地址
return super.clone();
}
// 深拷贝实现(手动递归复制引用类型)
public Person deepClone() {
try {
// 先拷贝基本类型(利用浅拷贝的基础)
Person copy = (Person) super.clone();
// 对引用类型单独创建新对象并复制内容
copy.address = new Address(
this.address.getCity(),
this.address.getStreet()
);
return copy;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("深拷贝失败", e);
}
}
// 重写toString方便打印
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", address=" + address + "}";
}
}
2. 测试代码(验证深拷贝与浅拷贝的区别)
java
public class PrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 1. 创建原始对象
Address originalAddr = new Address("北京", "朝阳区");
Person originalPerson = new Person("张三", 25, originalAddr);
System.out.println("原始对象: " + originalPerson);
// 2. 浅拷贝测试
Person shallowCopy = (Person) originalPerson.clone();
// 修改拷贝对象的引用类型成员(Address)
shallowCopy.getAddress().setCity("上海"); // 注意:这里修改的是共享的Address对象
shallowCopy.setName("浅拷贝-张三"); // 修改基本类型(不影响原始对象)
System.out.println("浅拷贝后原始对象: " + originalPerson); // 原始对象的Address被修改了!
System.out.println("浅拷贝对象: " + shallowCopy);
// 3. 深拷贝测试(先恢复原始对象的Address,避免受浅拷贝影响)
originalAddr.setCity("北京"); // 恢复地址为北京
Person deepCopy = originalPerson.deepClone();
// 修改拷贝对象的引用类型成员(Address)
deepCopy.getAddress().setCity("广州"); // 这里修改的是深拷贝创建的新Address对象
deepCopy.setName("深拷贝-张三"); // 修改基本类型(不影响原始对象)
System.out.println("深拷贝后原始对象: " + originalPerson); // 原始对象的Address未被修改
System.out.println("深拷贝对象: " + deepCopy);
}
}
关键总结
-
浅拷贝 :通过
super.clone()实现,仅复制基本类型和引用地址,引用类型成员共享内存(如示例中浅拷贝后修改Address会影响原始对象)。 -
深拷贝 :在浅拷贝基础上,对引用类型成员(如
Address)手动创建新对象并复制内容,实现完全独立(如示例中深拷贝后修改Address不影响原始对象)。 -
适用场景:
- 若对象仅含基本类型,浅拷贝高效且足够。
- 若对象含引用类型且需独立修改,必须用深拷贝(否则会出现 "修改拷贝影响原始" 的意外问题)。
Java 中通过 Object.clone() 实现的克隆默认是浅拷贝,这意味着对于对象中的引用类型成员,仅复制其内存地址(即新对象与原对象共享该引用类型实例)。如果业务中需要克隆后的对象与原对象完全独立(修改一方不影响另一方),就必须手动实现深拷贝,否则可能因共享数据导致逻辑错乱。
为什么浅拷贝会导致问题?
当对象包含引用类型成员(如自定义对象、集合、数组等)时,浅拷贝会让原对象和克隆对象的引用成员指向同一块内存。此时修改任意一方的引用成员属性,会同时影响另一方,可能导致数据不一致或逻辑错误。
深拷贝的实现方式
在 Java 中,实现深拷贝的常见方式有两种:
1. 手动递归克隆(适合简单对象)
对所有引用类型成员手动创建新实例,并复制其属性,确保引用链上的所有对象都被克隆。
java
class User implements Cloneable {
private String name;
private Address address; // 引用类型
public User(String name, Address address) {
this.name = name;
this.address = address;
}
// 深拷贝实现
@Override
protected User clone() throws CloneNotSupportedException {
// 先浅拷贝基本类型
User clone = (User) super.clone();
// 手动克隆引用类型成员(关键步骤)
clone.address = this.address.clone(); // 假设Address也实现了深拷贝
return clone;
}
// getter/setter
public Address getAddress() { return address; }
}
class Address implements Cloneable {
private String city;
public Address(String city) {
this.city = city;
}
// Address自身的深拷贝
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address) super.clone(); // 因Address仅含基本类型,浅拷贝即可
}
public void setCity(String city) { this.city = city; }
public String getCity() { return city; }
}
2. 序列化方式(适合复杂对象)
通过将对象序列化到流中,再反序列化为新对象,实现深拷贝(需确保所有成员都支持序列化)。
java
import java.io.*;
class User implements Serializable {
private String name;
private Address address; // 引用类型需也实现Serializable
// 深拷贝(基于序列化)
public User deepClone() throws IOException, ClassNotFoundException {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化(生成新对象)
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject();
}
// ... 其他代码同上
}
class Address implements Serializable { // 必须实现Serializable
private String city;
// ... 其他代码同上
}
验证深拷贝的必要性
java
public class DeepCopyDemo {
public static void main(String[] args) throws Exception {
Address addr = new Address("北京");
User original = new User("张三", addr);
// 浅拷贝(问题场景)
User shallowClone = (User) original.clone(); // 假设User未重写clone实现深拷贝
shallowClone.getAddress().setCity("上海");
System.out.println(original.getAddress().getCity()); // 输出"上海"(原对象被影响)
// 深拷贝(正确场景)
User deepClone = original.deepClone(); // 或使用手动递归的clone方法
deepClone.getAddress().setCity("广州");
System.out.println(original.getAddress().getCity()); // 输出"北京"(原对象不受影响)
}
}
总结
- 浅拷贝 :仅适合对象中全为基本类型(或不可变引用类型如
String)的场景,实现简单但存在共享引用的风险。 - 深拷贝:当对象包含可变引用类型时必须使用,确保克隆对象与原对象完全独立,避免数据共享导致的逻辑错乱。
- 选择哪种方式取决于对象结构:简单对象用手动递归克隆,复杂对象(多层嵌套、集合等)用序列化更高效。
在实际开发中,若忽视浅拷贝的局限性,可能会导致难以排查的 Bug(如修改克隆对象意外影响原对象),因此需根据业务场景谨慎选择拷贝方式。