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

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

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


第一幕:搬家那天

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

他有两样东西:

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

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

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

于是新家里:

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

这时候问题来了。

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

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

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

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


第二幕:浅拷贝是什么

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

用 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 小时前
又一 Personal Agent 发布,帮我省下不止200刀。。。
后端
程序员清风1 小时前
AI时代建议学点自己真正感兴趣的!
java·后端·面试
Duang007_1 小时前
生产可观测性:从“系统慢“到“根因“的完整链路(Go / TypeScript)
后端·python·golang·typescript·prometheus
Wang's Blog2 小时前
Java框架 SpringCloud 快速入门: Nacos 配置热更新
java·spring·spring cloud
西索斯coding2 小时前
Kimi k2.7-code-highspeed 接入 Cline 教程:baseURL 路由变更 + model_id 正确写法
java·服务器·数据库·ai
stark张宇2 小时前
InnoDB锁机制全景图:全局锁、表锁、行锁、意向锁到底怎么用?
数据库·后端
用户094248568032 小时前
第29章:【OpenJDK中级篇综合实战】百万长连接网关的JVM稳定性工程
java·jvm
newerp2 小时前
pprof 火焰图(Flame Graph)阅读与热点代码重构实战
后端·程序员·go
吠品2 小时前
HTTPS 真身:HTTP 套了层 TLS
java·服务器·前端