深拷贝和浅拷贝:一次“搬家”事故

周五晚上,小刘抱着电脑来找我:"杨工,我改一个对象里的列表,结果另一个对象也跟着变了,这是见鬼了吗?"

我笑了:"没见鬼,你这是浅拷贝。"


第一幕:搬家那天

小刘租的房子到期,要搬去新小区。

他有两样东西:

  • 一个背包,里面装着身份证、钥匙;
  • 一个书架,上面放着几十本书。

搬家师傅问:"怎么搬?"

小刘说:"背包里的东西照原样复制一份;书架太重了,别搬书了,**给我一张纸条,写着'书架在老房子客厅'**就行。"

于是新家里:

  • 背包是全新的,身份证、钥匙都是复制品;
  • 书架没有搬过来,只有一张纸条,指向老房子的书架。

这时候问题来了。

小刘在新家,从"书架"上抽走一本书。

结果第二天他回老房子一看:老房子的书架上也少了一本书。

他懵了:"我没动老房子啊?"

我说:"你动了。因为你新家根本没有书架,你只是拿纸条找到了老房子的书架。你抽走的,是同一排书。"


第二幕:浅拷贝是什么

浅拷贝就是:外层复制了一份,但里面的引用对象没有复制,仍然指向同一个。

用 Java 来理解:

复制代码
class Person implements Cloneable {
    String name;
    List<String> books;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 浅拷贝
    }
}

super.clone() 会复制 name、books 这个引用,但不会复制 books 指向的那个 ArrayList。

所以:

复制代码
Person p1 = new Person();
p1.name = "小刘";
p1.books = new ArrayList<>();
p1.books.add("Java核心技术");

Person p2 = (Person) p1.clone();

p2.books.add("深入理解JVM");

System.out.println(p1.books); // 也会出现《深入理解JVM》

因为 p1.books 和 p2.books 指向的是同一个列表。

就像新家没有真正的书架,只有一张纸条,指向老房子的书架。


第三幕:深拷贝是什么

小刘说:"那我不想这样,我要新家也有自己的书架。"

我说:"那就得把书也搬过来。"

深拷贝就是:

  • 复制外层对象;
  • 再递归复制里面所有引用对象;
  • 直到所有东西都是独立的副本。

搬家师傅这次说:"行,书架我也给你搬一份,书也照原样复制一份。"

于是新家有了:

  • 自己的背包;
  • 自己的书架;
  • 自己的书。

这时候小刘在新家抽走一本书,老房子完全不受影响。

用代码表示就是:

复制代码
class Person implements Cloneable {
    String name;
    List<String> books;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person copy = (Person) super.clone();
        // 把内部集合也复制一份
        copy.books = new ArrayList<>(this.books);
        return copy;
    }
}

这时候:

复制代码
Person p1 = new Person();
p1.name = "小刘";
p1.books = new ArrayList<>();
p1.books.add("Java核心技术");

Person p2 = (Person) p1.clone();

p2.books.add("深入理解JVM");

System.out.println(p1.books); // 只有《Java核心技术》
System.out.println(p2.books); // 《Java核心技术》《深入理解JVM》

两个对象彻底独立。


第四幕:一句话分清它们

浅拷贝 :复制了对象外壳,但内部引用还共享。

深拷贝:外壳和内部引用都复制,得到完全独立的副本。

再打个比方:

拷贝方式 搬家比喻 修改内部数据会怎样
浅拷贝 新家只拿到一张"书架地址纸条" 改一个,另一个也跟着变
深拷贝 新家搬来一整套新书架和新书 两个互不影响

第五幕:Java 里常见的深拷贝方式

小刘问:"那实际开发里怎么深拷贝?"

我说:"常见有几种。"

  1. 手动 new + 赋值

    最朴素,也最可控。

  2. 重写 clone,逐层复制

    适合结构不复杂的对象。

  3. 序列化反序列化

    比如用 JSON:

    复制代码
    String json = JSON.toJSONString(p1);
    Person p2 = JSON.parseObject(json, Person.class);

    只要对象能序列化,就能一次性深拷贝。

  4. 构造器拷贝

    给对象写一个拷贝构造函数:

    复制代码
    public Person(Person other) {
        this.name = other.name;
        this.books = new ArrayList<>(other.books);
    }

尾声

小刘合上电脑:"所以浅拷贝是'复制了指针',深拷贝是'复制了指针指向的东西'。"

我点点头:"再补一句:浅拷贝快,但可能共享状态;深拷贝安全,但成本高。 如果对象里有集合、嵌套对象、可变引用,千万别以为复制一份就安全了。"

他想了想:"那我以后看到 clone(),第一反应不是'复制好了',而是'里面有没有共享引用'。"

我说:"这就对了。浅拷贝复制的是门牌号,深拷贝复制的是整栋房子。"

相关推荐
学到老活到老1 分钟前
第 22 集:Filter、Interceptor、AOP 都能拦截,为什么不能互换?
java·后端·spring
sun032226 分钟前
【JDK 动态代理】 和 【CGLIB 代理】对比
java·数据库·sql
文丑颜不良啊26 分钟前
LeetCode1261~1280题目解析
java·leetcode
郭小李28 分钟前
spring 中的 @EnableWebMvc注解和WebMvcConfigurer接口
java·后端·spring
卷无止境35 分钟前
Skill 的安全边界在哪里:风险来源、验证思路与用户可用的防线
后端·python
香蕉大香蕉(06敬畏市场)1 小时前
高频八股 · JVM(8)
java·jvm·面试·八股·大厂实习
星核0penstarry1 小时前
企业 API 向后兼容实战:版本控制、废弃字段通知与灰度发布策略
人工智能·后端·api·接口隔离原则·版本控制·架构设计·后端架构
养军博客1 小时前
spirngSecurity 鉴权教程02
java·springsecurity
CoderYanger2 小时前
Java EE 进阶:5. Spring IoC&DI(第二弹):Spring IoC
java·开发语言·spring boot·spring·面试·职场和发展·java-ee
用户别睡觉2 小时前
自己搭的中文 PDF RAG 接了 Redis 缓存、跑了 RAGAS,以为能写“提速多少、准确率多少”,结果两个都没按预期来。
后端