一次令人窒息的面试体验
事情发生在我上一份工作离职后。那段时间信心爆棚,觉得自己对JVM烂熟于心,GC啊、内存模型啊,轻轻松松就能应对。
结果到了美团面试,面试官一开口:“你能说说深拷贝和浅拷贝的区别吗?”
我心里一乐,这不送分题嘛!
“浅拷贝就是只拷贝对象的第一层,深拷贝是连引用的对象也都复制一份。”
说完我就等着夸奖,结果面试官一挑眉:“那如果对象里嵌套了多个引用,浅拷贝和深拷贝在内存上分别会是怎样的结构?”
……
完了,脑子顿时嗡嗡响,我竟然说不出具体场景,只能干巴巴地扯了点 clone() 的话题,最后草草结束。
回家路上我一直在想:这么简单的知识点,为什么我在关键时刻说不清楚?
那晚我重新捡起了笔记,从JVM内存模型、对象结构到实际代码实现,一个点一个点抠,才发现“深拷贝 vs 浅拷贝”远没有我们想象中那么简单。
什么是浅拷贝?
我们先从“浅拷贝”聊起。
浅拷贝指的是:只复制当前对象本身的字段,对于引用类型的字段,只复制引用地址,不复制其指向的对象本身。
说人话就是——表面复制一份,看起来和原来一样,其实底层的引用指向的还是同一块内存区域。
举个小例子:
当你用 clone() 创建了一个新对象,Person 对象本体是复制了,但里面的 Address 是引用,还是指向原来的地址。
所以,当你修改 clone 出来的 address 的城市名,原对象也跟着变了。这就是浅拷贝“表面忠诚”的真面目。
深拷贝又是什么?
深拷贝的定义相对复杂一些。
它不仅复制当前对象本身的字段,而且对引用类型字段也会“递归”地复制一份新的对象出来,整个对象链条都脱离了原始对象。
形象点说,浅拷贝是拍了张照片,外形一模一样但连着同一条神经;而深拷贝是另造了一个灵魂,也有自己的脑袋、手脚。
用代码实现深拷贝,通常有两种方式:
方式一:实现 Cloneable 接口并在每一层都重写 clone() 方法
方式二:使用序列化(推荐用于大型复杂结构)
缺点是效率不如 clone(),但胜在稳定、通用。
JVM视角下的拷贝到底发生了什么?
这时,我们来切换一下视角,用 JVM 的眼睛看拷贝到底干了啥。
当一个对象被创建时,它会被分配在 堆内存 中,所有引用类型字段也同样如此。
浅拷贝时,新对象只复制了第一层结构,其引用字段指向的地址与原对象相同,因此依赖了相同的堆空间。
而深拷贝则是为引用字段也申请新的堆内存地址,并将其内容一并复制。因此,在 JVM 内存中,深拷贝会产生更多对象实例,但带来了真正的独立性。
这也是为什么深拷贝开销更大,性能要慎重考虑的原因。
一道经典社招题:你能画出深浅拷贝的结构图吗?
在字节跳动面试中,我就被要求“在纸上画出一段对象的浅拷贝和深拷贝的内存图”,还要解释 JVM 中的堆和栈的作用。
当时我画了这么一个结构:
- 原始对象 Order,有一个指向 products 的引用
- 浅拷贝的 OrderCopy 也有一个指向相同 products 的引用(箭头指向同一块地址)
- 深拷贝的 OrderCopy 拥有自己独立的 products,箭头各自指向新地址
面试官点点头,说:“这个图比你嘴上说十分钟都有用。”
从那以后,我记住了:面试,不光要懂,更要表达得清楚。
拷贝时的那些坑:你踩过几个?
1. 没有实现 Cloneable 却调用 clone()
这是 Java 的一个“坑设定”。如果你不显式实现 Cloneable 接口,调用 clone() 会抛出 CloneNotSupportedException,即便你写了 clone() 方法。
2. 忘记递归 clone 引用字段
很多同学以为 clone() 一调用万事大吉,殊不知你得自己去处理嵌套对象,否则默认还是浅拷贝。
3. 用 BeanUtils.copyProperties() 以为是深拷贝
这个常见在 Spring 项目中,实际上它复制的是“值”,但依旧是浅拷贝。引用字段还是共用的,改了一个变两个。
工作中我们该怎么选?
这其实没有绝对标准,要看你的业务需求:
- 如果对象结构简单,或你明确知道不会修改引用字段,浅拷贝足矣。
- 如果需要真正隔离两个对象,防止互相影响,务必用深拷贝。
- 如果是高性能场景(比如游戏逻辑、交易撮合引擎),要避免深拷贝带来的GC压力,可能考虑对象池或写时复制(COW)策略。
写在最后
那次美团面试我没通过,但我很感谢那个面试官,因为他让我重新认识了“拷贝”这个在 Java 世界中最基础却最容易被忽略的知识点。
现在我每次准备面试,都会写写画画,把脑中知识真正“物化”,就像做一次深拷贝。
最后送大家一句话:
技术不是记忆的堆叠,而是理解和表达的艺术。
会用,更要说得明白、画得出来。
你最近有没有被“拷贝”题难住过?欢迎在留言区聊聊你的故事呀!
END
如果你觉得这篇文章对你有帮助,记得 转发 + 在看,支持小米继续更新更多“JVM 社招面试题故事集”系列喔!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
我们下期见~