Java 深拷贝和浅拷贝的区别是什么?
2026年01月19日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
面试官提出这个问题,主要想考察以下几个维度的能力:
- 对 Java 对象模型与内存理解的基本功:候选人是否清楚对象、引用、基本类型在内存中的存储方式。
- 对
Object.clone()方法的掌握程度:是否了解默认clone()方法的浅拷贝行为,以及Cloneable接口的作用。 - 对数据隔离与对象完整性的认知:是否理解在复杂对象(包含引用类型成员)的复制场景下,浅拷贝可能带来的副作用,以及何时需要深拷贝来确保数据的完全独立。
- 实践应用与设计能力:能否根据具体场景(如:DTO 对象转换、缓存对象复用、防止外部修改影响内部状态)选择合适的拷贝策略,并知道如何正确实现深拷贝。
- 对不可变对象的理解:这常常是追问点。面试官想知道你是否意识到,如果对象的所有成员都是不可变(如
String,Integer)或基本类型,那么浅拷贝和深拷贝的效果是等同的,从而引出“防御性拷贝”和“不可变性”等高级主题。
核心答案
- 浅拷贝 只复制对象本身(包括其基本类型字段),但对于对象内部的引用类型字段,它复制的是引用地址。因此,拷贝对象和原对象会共享这些引用类型的成员对象。修改任何一个对象的引用成员,会影响到另一个。
- 深拷贝 则是 “彻底” 的复制,它会递归地复制对象本身以及其内部所有引用类型字段指向的整个对象链,直到所有依赖的对象都是基本类型或不可变对象为止。拷贝完成后,两个对象完全独立,互不影响。
Java 中 Object 类默认的 protected native Object clone() 方法实现的是浅拷贝。要使用它,类必须实现 Cloneable 接口并重写 clone() 方法(通常提升为 public)。
深度解析
原理/机制
- 浅拷贝 在内存中创建了一个新的对象实例,并将原对象的所有字段值 “按位复制” 到新对象。对于引用字段,复制的 “值” 就是那个指向堆内存中另一个对象的地址。你可以把它想象成复印了一份个人简历,但 “工作经历” 那一栏指向的是同一份 Word 文档原件。
- 深拷贝 则是沿着引用链进行递归复制。它不仅创建了新对象,还为该对象内部的每一个引用字段所指向的对象创建了副本,并让新对象的引用字段指向这些副本。这就像不仅复印了简历,还把 “工作经历” 文档也重新创建并填写了一份一模一样的。
代码示例
我们用一个包含引用类型成员 (WorkExperience) 的 Resume 类来演示。
// 工作经历类
class WorkExperience implements Cloneable {
String company;
int years;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); // WorkExperience 本身只有基本类型/String,浅拷贝即可
}
}
// 简历类
class Resume implements Cloneable {
String name; // 基本类型/String
WorkExperience work; // 引用类型
// 【浅拷贝实现】
@Override
public Object shallowClone() throws CloneNotSupportedException {
return super.clone(); // 默认的 clone() 是浅拷贝
}
// 【深拷贝实现 - 方式1:递归调用 clone()】
@Override
public Object deepCloneByClone() throws CloneNotSupportedException {
Resume clone = (Resume) super.clone(); // 1. 浅拷贝本体
clone.work = (WorkExperience) this.work.clone(); // 2. 关键:手动拷贝引用成员
return clone;
}
// 【深拷贝实现 - 方式2:通过序列化 (更通用,但要求所有涉及对象可序列化)】
public Resume deepCloneBySerialization() 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 (Resume) ois.readObject();
}
}
// 测试
public class CopyTest {
public static void main(String[] args) throws Exception {
Resume r1 = new Resume();
r1.name = "张三";
r1.work = new WorkExperience();
r1.work.company = "A公司";
// 浅拷贝测试
Resume r2_shallow = (Resume) r1.shallowClone();
r2_shallow.work.company = "B公司"; // 修改拷贝对象的 work
System.out.println(r1.work.company); // 输出:B公司!原对象被影响了
// 深拷贝测试
Resume r3_deep = (Resume) r1.deepCloneByClone();
r3_deep.work.company = "C公司"; // 修改深拷贝对象的 work
System.out.println(r1.work.company); // 输出:B公司(或最初的A公司)。原对象不受影响
}
}
对比分析与最佳实践
| 特性 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制内容 | 对象本身 + 字段值(引用值) | 对象本身 + 引用链上的所有对象 |
| 内存与性能 | 速度快,内存占用少 | 速度慢(尤其对象图复杂时),内存占用多 |
| 独立性 | 拷贝对象与原对象共享引用成员 | 完全独立,互不影响 |
| 实现复杂度 | 简单,通常 super.clone() 即可 | 复杂,需要递归处理所有引用字段 |
| 适用场景 | 对象字段多为基本类型或不可变对象;需要快速复制且不关心共享状态 | 需要完全隔离数据,防止副作用;如传输对象、缓存副本、恢复快照 |
最佳实践:
- 优先考虑不可变性:设计类时,尽量让字段是
final和不可变的。这样,浅拷贝就足够安全,且性能最佳。 - 谨慎使用
Cloneable:clone()方法机制笨拙(受检异常、protected访问),且破坏了构造器封装。业界更推荐使用 “拷贝构造器” 或 “拷贝工厂方法”。// 更优雅的深拷贝实现 - 拷贝构造器 public Resume(Resume another) { this.name = another.name; this.work = new WorkExperience(another.work); // WorkExperience 也需提供拷贝构造器 } - 复杂对象的深拷贝:对于复杂对象图(如嵌套集合),使用序列化/反序列化、或第三方库(如 Apache Commons Lang 的
SerializationUtils.clone(), Jackson/Gson 的序列化转换)是更可靠的选择,但需注意性能开销和所有相关类必须可序列化。
常见误区
- 认为实现
Cloneable接口就能自动深拷贝:大错特错。Cloneable只是一个标记接口,Object.clone()的默认行为永远都是浅拷贝。 - 在深拷贝实现中漏掉某些引用字段:这是最常见的实现错误,会导致 “半深半浅” 的拷贝,隐患更大。
- 忽视循环引用:如果对象图中存在循环引用(A 引用 B, B 又引用 A),简单的递归
clone()会导致栈溢出。序列化方式或使用IdentityHashMap记录已拷贝对象可以解决此问题。
总结
浅拷贝与深拷贝的核心区别在于对引用类型字段的处理方式:浅拷贝共享引用,深拷贝创建独立副本。在实际开发中,应基于数据隔离性的需求和性能考量来选择,并优先采用拷贝构造器等更清晰、安全的方式来实现深度复制。