【Java】浅拷贝 VS 深拷贝:核心差异 + 实现方式 + 避坑指南

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是Java面试中考察「对象复制」的高频考点,核心区别在于是否复制引用类型成员的底层对象

一、核心结论

类型 复制范围 引用类型成员处理 核心特点
浅拷贝 仅复制对象本身,不复制引用类型成员指向的对象 引用类型成员仅复制"引用地址",新旧对象共享同一个底层对象 拷贝速度快,存在"修改一个影响另一个"的风险
深拷贝 复制对象本身 + 所有引用类型成员指向的对象 引用类型成员复制"底层对象",新旧对象完全独立 拷贝速度慢,新旧对象互不影响

二、通俗理解(用"文件夹"比喻讲透)

把对象想象成一个「文件夹」,里面既有「文件」(基本类型成员,如int/String),也有「子文件夹快捷方式」(引用类型成员,如User/数组):

  • 浅拷贝:复制一个新文件夹,里面的文件直接复制,子文件夹只复制"快捷方式"(指向原文件夹)。修改新文件夹里的子文件夹内容,原文件夹的子文件夹也会变;
  • 深拷贝:复制一个新文件夹,里面的文件+子文件夹(包括子文件夹里的所有内容)都完整复制。修改新文件夹的任何内容,都不会影响原文件夹。

三、原理分析

1. 浅拷贝的底层逻辑

  • 实现方式:实现 Cloneable 接口,重写 Object 类的 clone() 方法(默认浅拷贝);

  • 复制规则:

    • 基本类型成员(int/boolean/char等):复制值(新对象有独立副本);
    • 引用类型成员(对象/数组/集合等):复制引用地址(新旧对象指向同一个底层对象)。

2. 深拷贝的底层逻辑

  • 实现方式:在浅拷贝基础上,对所有引用类型成员递归调用拷贝方法,或通过序列化实现;

  • 复制规则:

    • 基本类型成员:复制值;
    • 引用类型成员:创建新的底层对象,复制其所有内容(新旧对象的引用类型成员指向不同底层对象)。

四、代码演示

先定义基础类(包含引用类型成员)

java 复制代码
// 引用类型成员类(地址)
class Address {
    private String city;

    public Address(String city) {
        this.city = city;
    }

    // get/set方法
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
}

// 待拷贝的主类
class Person implements Cloneable {
    private String name; // 基本类型(String是不可变类,特殊的引用类型)
    private int age;      // 基本类型
    private Address addr; // 引用类型成员

    public Person(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    // get/set方法
    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 getAddr() { return addr; }
    public void setAddr(Address addr) { this.addr = addr; }

    // ===== 1. 浅拷贝实现(默认clone())=====
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 调用Object的clone(),默认浅拷贝
        return super.clone();
    }

    // ===== 2. 深拷贝实现(手动处理引用类型成员)=====
    protected Object deepClone() throws CloneNotSupportedException {
        // 第一步:浅拷贝当前对象
        Person clonePerson = (Person) super.clone();
        // 第二步:对引用类型成员单独拷贝(创建新的Address对象)
        clonePerson.addr = new Address(clonePerson.addr.getCity());
        return clonePerson;
    }
}

测试浅拷贝

java 复制代码
public class CopyDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原对象
        Address addr = new Address("北京");
        Person p1 = new Person("张三", 20, addr);

        // 2. 浅拷贝得到p2
        Person p2 = (Person) p1.clone();

        // 3. 修改p2的引用类型成员(addr)
        p2.getAddr().setCity("上海");

        // 4. 结果:p1的addr也被修改(共享底层对象)
        System.out.println("p1的地址:" + p1.getAddr().getCity()); // 输出:上海
        System.out.println("p2的地址:" + p2.getAddr().getCity()); // 输出:上海

        // 补充:基本类型成员互不影响(浅拷贝复制了值)
        p2.setAge(25);
        System.out.println("p1的年龄:" + p1.getAge()); // 输出:20
        System.out.println("p2的年龄:" + p2.getAge()); // 输出:25
    }
}

测试深拷贝(解决浅拷贝问题)

java 复制代码
public class CopyDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原对象
        Address addr = new Address("北京");
        Person p1 = new Person("张三", 20, addr);

        // 2. 深拷贝得到p2
        Person p2 = (Person) p1.deepClone();

        // 3. 修改p2的引用类型成员(addr)
        p2.getAddr().setCity("上海");

        // 4. 结果:p1的addr不变(新旧对象独立)
        System.out.println("p1的地址:" + p1.getAddr().getCity()); // 输出:北京
        System.out.println("p2的地址:" + p2.getAddr().getCity()); // 输出:上海
    }
}

五、深拷贝的其他实现方式

方式1:序列化(推荐,适合复杂对象)

通过序列化将对象转为字节流,再反序列化生成新对象(自动实现深拷贝):

java 复制代码
class Person implements Serializable {
    private String name;
    private int age;
    private Address addr; // Address也需实现Serializable

    // 深拷贝方法(序列化)
    public Person deepCloneBySerialize() throws IOException, ClassNotFoundException {
        // 1. 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        // 2. 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Person) ois.readObject();
    }
}

方式2:手动new对象(简单场景)

直接创建新对象,逐个赋值(包括引用类型成员):

java 复制代码
Person p2 = new Person(p1.getName(), p1.getAge(), new Address(p1.getAddr().getCity()));

六、追问

追问1:String 是引用类型,为什么浅拷贝时修改不会互相影响?

答:String 是「不可变类」------修改 String 本质是创建新的 String 对象,而非修改原有对象内容。因此浅拷贝时,新旧对象的 String 成员指向不同的新对象,不会互相影响。

追问2:浅拷贝有什么应用场景?

答:浅拷贝速度快、开销小,适合以下场景:

  • 对象中无引用类型成员;
  • 引用类型成员是不可变类(如 String/Integer);
  • 不需要修改引用类型成员的场景。

追问3:深拷贝的优缺点?

答:

  • 优点:新旧对象完全独立,无修改互相影响的风险;
  • 缺点:拷贝速度慢、内存开销大(需复制所有引用类型成员的底层对象),递归拷贝可能导致栈溢出(复杂对象)。

追问:请说说深拷贝和浅拷贝的区别?

答:

  1. 核心区别:浅拷贝仅复制对象本身和基本类型成员的值,引用类型成员仅复制引用地址(新旧对象共享底层对象);深拷贝不仅复制对象本身,还会复制所有引用类型成员的底层对象(新旧对象完全独立);
  2. 表现差异:浅拷贝时修改引用类型成员,原对象会受影响;深拷贝时修改任何成员,原对象都不受影响;
  3. 实现方式
    • 浅拷贝:实现 Cloneable 接口,重写 Object.clone();
    • 深拷贝:递归拷贝引用类型成员、序列化/反序列化、手动new对象;
  1. 适用场景:浅拷贝适合无引用类型成员或引用类型为不可变类的场景(效率高);深拷贝适合需要完全隔离新旧对象的场景(如多线程修改、对象持久化)。

总结

  1. 核心区别:是否复制引用类型成员的底层对象(浅拷贝不复制,深拷贝复制);
  2. 关键表现:浅拷贝共享引用类型底层对象,深拷贝完全独立;
  3. 实现方式:浅拷贝用 clone(),深拷贝用递归/序列化/手动new;
  4. 面试关键:结合「哈希表/多线程」场景说明深拷贝的必要性,体现实战理解。

简单说:浅拷贝是"复制快捷方式",深拷贝是"复制全部内容"~

相关推荐
盐水冰1 小时前
【Redis】学习(3)Redis的Java客户端
java·redis·学习
阿星仔6661 小时前
claude code switch安装使用指南:一键切换多Claude API
java
weixin199701080161 小时前
淘宝客商品详情页前端性能优化实战
java·前端·python·性能优化
程途知微1 小时前
Java 内存模型 (JMM) 与 volatile 底层实现
java·后端
Joker Zxc1 小时前
【前端基础(Javascript部分)】5、JavaScript的循环语句
开发语言·前端·javascript
何中应2 小时前
IDEA中三个很方便的设置
java·ide·intellij-idea
jing-ya2 小时前
day 50 图论part2
java·算法·深度优先·图论
手握风云-2 小时前
Spring AI:让大模型住进 Spring 生态(二)
java·后端·spring