原型模式中的深浅拷贝

在原型模式(Prototype Pattern)中,深拷贝(Deep Copy)浅拷贝(Shallow Copy) 是实现对象复制时的两种核心方式,它们的区别在于对对象中引用类型成员的处理方式不同。

1. 浅拷贝(Shallow Copy)

浅拷贝会创建一个新对象,然后复制原对象中所有的基本数据类型成员 (如 int、float、string 等),但对于引用类型成员(如对象、数组、集合等),仅复制其引用地址,而非引用指向的实际对象。

  • 特点:新对象和原对象的引用类型成员共享同一块内存空间,修改其中一个会影响另一个。
  • 适用场景:对象中仅包含基本数据类型,或无需独立修改引用类型成员时。

2. 深拷贝(Deep Copy)

深拷贝会创建一个新对象,不仅复制原对象的基本数据类型成员,还会递归复制所有引用类型成员所指向的实际对象,即新对象的引用类型成员与原对象的引用类型成员完全独立,占用不同的内存空间。

  • 特点:新对象和原对象完全独立,修改其中一个的引用类型成员不会影响另一个。
  • 适用场景:对象中包含引用类型成员,且需要复制后独立修改时。

举例说明

假设存在一个Person类,包含基本类型name和引用类型addressAddress类的实例):

java 复制代码
class Address {
    String city;
    // 构造函数、getter、setter...
}

class Person {
    String name; // 基本类型
    Address address; // 引用类型
    // 构造函数、getter、setter...
}

浅拷贝 :复制Person时,name会被复制为新值,但address仅复制引用,新Person和原Personaddress指向同一个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的内容,使新Personaddress完全独立。

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);
    }
}

关键总结

  1. 浅拷贝 :通过 super.clone() 实现,仅复制基本类型和引用地址,引用类型成员共享内存(如示例中浅拷贝后修改 Address 会影响原始对象)。

  2. 深拷贝 :在浅拷贝基础上,对引用类型成员(如 Address)手动创建新对象并复制内容,实现完全独立(如示例中深拷贝后修改 Address 不影响原始对象)。

  3. 适用场景:

    • 若对象仅含基本类型,浅拷贝高效且足够。
    • 若对象含引用类型且需独立修改,必须用深拷贝(否则会出现 "修改拷贝影响原始" 的意外问题)。

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(如修改克隆对象意外影响原对象),因此需根据业务场景谨慎选择拷贝方式。

相关推荐
2501_941111462 小时前
C++中的原型模式
开发语言·c++·算法
进阶的猿猴2 小时前
easyExcel实现单元格合并
java·excel
亿坊电商2 小时前
PHP框架的资源管理机制如何优雅适配后台任务?
开发语言·php
VBA63372 小时前
YZ系列工具之YZ09: VBA_Excel之读心术
开发语言
小许学java2 小时前
MySQL-触发器
java·数据库·mysql·存储过程·触发器
pro_or_check2 小时前
自然语言编程:从一段Perl程序说起
开发语言
JEECG低代码平台2 小时前
【2025/11】GitHub本月热度排名前十的开源Java项目
java·开源·github
百***86052 小时前
Spring BOOT 启动参数
java·spring boot·后端
跟着珅聪学java2 小时前
Spring Boot 中整合 MySQL 并打印 SQL 日志
java·spring boot