Java深拷贝怎么实现?详解方法与技巧
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《Java深拷贝实现方法详解》,聊聊,我们一起来看看吧!
深拷贝在Java中意味着新对象及其引用的可变对象都是独立副本。1. 通过序列化与反序列化实现深拷贝,适用于复杂对象图,使用简单但性能开销大且需实现Serializable接口;2. 手动递归拷贝通过拷贝构造器或deepCopy方法实现,灵活可控但代码冗余、易出错,适合不可序列化或性能敏感场景。两种方式各有优劣,根据实际需求选择。
在Java中,实现对象的深拷贝,核心在于确保新创建的对象不仅拥有与原对象相同的值,更重要的是,它所引用的所有可变对象(比如集合、自定义对象等)也都是全新的独立副本,而非共享引用。这意味着对新对象的任何修改,都不会影响到原对象及其内部结构,反之亦然。实现深拷贝的常见方案主要有:通过序列化与反序列化、手动递归拷贝(包括使用拷贝构造器或克隆方法)、以及利用某些第三方库。

解决方案
实现Java对象的深拷贝,我个人比较倾向于两种主要策略,它们各有侧重,具体选择看场景和对象的复杂程度。

1. 基于序列化与反序列化
这是实现深拷贝的一种相对“懒人”且强大的方式,尤其适合处理复杂对象图。原理很简单:将对象写入一个输出流(比如ByteArrayOutputStream
),再从输入流(ByteArrayInputStream
)中读出来。这样“走一圈”下来,你得到的就是一个全新的、完全独立的深拷贝对象。

import java.io.*; public class DeepCopyUtil { @SuppressWarnings("unchecked") public static <T extends Serializable> T deepCopy(T obj) { try { // 写入字节流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); // 从字节流中读取 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return (T) ois.readObject(); } catch (IOException | ClassNotFoundException e) { // 实际项目中可能需要更细致的异常处理 System.err.println("深拷贝失败: " + e.getMessage()); throw new RuntimeException("深拷贝操作失败", e); } } // 示例类 public static class Address implements Serializable { private static final long serialVersionUID = 1L; private String city; private String street; public Address(String city, String street) { this.city = city; this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } @Override public String toString() { return "Address{" + "city='" + city + '\'' + ", street='" + street + '\'' + '}'; } } public static class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; private Address address; // 引用类型 public Person(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } 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 getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}'; } } public static void main(String[] args) { Address originalAddress = new Address("北京", "朝阳区"); Person originalPerson = new Person("张三", 30, originalAddress); System.out.println("原始对象: " + originalPerson); // 执行深拷贝 Person copiedPerson = DeepCopyUtil.deepCopy(originalPerson); System.out.println("拷贝对象: " + copiedPerson); // 修改拷贝对象的内部引用 copiedPerson.getAddress().setStreet("海淀区"); copiedPerson.setName("李四"); System.out.println("修改后原始对象: " + originalPerson); System.out.println("修改后拷贝对象: " + copiedPerson); // 验证是否是深拷贝 System.out.println("原始地址对象和拷贝地址对象是否是同一个引用: " + (originalPerson.getAddress() == copiedPerson.getAddress())); } }
运行上述代码,你会发现修改copiedPerson
的地址后,originalPerson
的地址并没有改变,这正是深拷贝的魅力。
2. 手动递归拷贝(拷贝构造器/克隆方法)
这种方式需要你亲自去实现拷贝逻辑。对于每个包含引用类型字段的类,你都需要编写代码来创建这些引用类型字段的新实例,并复制它们的内容。
// 假设有上面的Address类,但它不再需要实现Serializable // public class Address { ... } public class PersonWithManualCopy { private String name; private int age; private DeepCopyUtil.Address address; // 使用上面的Address类 public PersonWithManualCopy(String name, int age, DeepCopyUtil.Address address) { this.name = name; this.age = age; this.address = address; } // 拷贝构造器实现深拷贝 public PersonWithManualCopy(PersonWithManualCopy other) { this.name = other.name; // String是不可变的,直接赋值即可 this.age = other.age; // 基本类型直接赋值 // 对于可变引用类型,必须创建新实例并拷贝其内容 this.address = new DeepCopyUtil.Address(other.address.getCity(), other.address.getStreet()); } // 或者提供一个深拷贝方法 public PersonWithManualCopy deepCopy() { return new PersonWithManualCopy(this.name, this.age, new DeepCopyUtil.Address(this.address.getCity(), this.address.getStreet())); } // Getters and Setters (略) 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 DeepCopyUtil.Address getAddress() { return address; } public void setAddress(DeepCopyUtil.Address address) { this.address = address; } @Override public String toString() { return "PersonWithManualCopy{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}'; } public static void main(String[] args) { DeepCopyUtil.Address originalAddress = new DeepCopyUtil.Address("上海", "浦东新区"); PersonWithManualCopy originalPerson = new PersonWithManualCopy("王五", 25, originalAddress); System.out.println("原始对象: " + originalPerson); // 使用拷贝构造器 PersonWithManualCopy copiedPerson = new PersonWithManualCopy(originalPerson); // 或者使用deepCopy方法 // PersonWithManualCopy copiedPerson = originalPerson.deepCopy(); System.out.println("拷贝对象: " + copiedPerson); copiedPerson.getAddress().setStreet("闵行区"); copiedPerson.setName("赵六"); System.out.println("修改后原始对象: " + originalPerson); System.out.println("修改后拷贝对象: " + copiedPerson); System.out.println("原始地址对象和拷贝地址对象是否是同一个引用: " + (originalPerson.getAddress() == copiedPerson.getAddress())); } }
手动拷贝的好处是你可以完全控制拷贝过程,不需要依赖Serializable
接口。但缺点也很明显,如果对象结构复杂,嵌套层级深,工作量会非常大,而且容易出错。
为什么浅拷贝不能满足所有需求?
浅拷贝,顾名思义,只是复制了对象本身以及它所包含的基本类型字段的值。对于引用类型字段,它复制的仅仅是这些字段的引用地址,而不是它们指向的实际对象。这就像你复印了一份文件,但文件里提到的“那本书”你只是记下了书名,并没有真的去买一本新的书。
举个例子,假设你有一个订单
对象,它内部有一个商品列表
(List
)。如果你对这个订单
对象进行浅拷贝,那么新的订单
对象会和原订单
对象共享同一个商品列表
的引用。这意味着,如果你通过新订单
对象向商品列表
中添加或删除商品,原订单
对象的商品列表
也会同步发生变化。这在很多业务场景下是不可接受的,因为它破坏了对象的独立性,可能导致数据不一致、难以追踪的bug,甚至引起并发问题。
简单来说,当你的对象内部包含可变引用类型(比如List
、Map
、自定义对象等),并且你希望新对象与原对象完全独立,互不影响时,浅拷贝就无法满足需求了。你可能需要修改拷贝后的对象,但又不希望这些修改影响到原始对象的状态,这时候深拷贝就成了必需品。
序列化实现深拷贝的优缺点及适用场景
使用序列化来实现深拷贝,我个人觉得它在很多情况下都是一个非常便捷且可靠的方案。
优点:
- 简单易行: 对于复杂的对象图,只要所有参与深拷贝的对象都实现了
Serializable
接口,你几乎不需要编写额外的拷贝逻辑,只需调用工具方法即可。这极大地简化了代码量,减少了出错的可能性。 - 自动处理嵌套: 序列化机制会递归地处理对象内部的所有引用,只要它们也是可序列化的,就能自动实现深层拷贝,无需你手动遍历。
- 通用性强: 这种方法不仅可以用于深拷贝,也是Java对象持久化、网络传输(RMI)等场景的基础。
缺点:
- 性能开销: 序列化和反序列化涉及到I/O操作(即使是内存中的字节流),这通常比直接的内存复制或手动字段拷贝要慢。对于性能要求极高的场景,这可能是一个瓶颈。
- 侵入性: 所有需要被深拷贝的对象及其内部引用的对象,都必须实现
Serializable
接口。如果有些第三方库的对象没有实现这个接口,你就无法直接使用这种方法。 transient
字段: 被transient
关键字修饰的字段在序列化时会被忽略,这意味着它们不会被拷贝。如果你需要拷贝这些字段,序列化方式就不适用了。- 版本兼容性: 如果对象的结构(比如字段类型、名称)在不同版本间发生变化,
serialVersionUID
的管理会变得重要,否则反序列化可能会失败。
适用场景:
- 对象结构复杂,嵌套层级深: 这是序列化深拷贝最能体现优势的场景,可以避免大量手动编码。
- 性能要求不极致: 在大多数业务应用中,序列化的性能开销通常在可接受范围内。
- 对象需要跨进程/网络传输: 如果你的对象本来就需要序列化以进行传输或持久化,那么顺便用它来实现深拷贝就非常自然了。
- 快速原型开发: 当你需要快速实现一个深拷贝功能,而不想投入太多精力去手动维护拷贝逻辑时。
手动实现深拷贝的挑战与最佳实践
手动实现深拷贝虽然灵活,但挑战不小,尤其当对象结构变得复杂时。
挑战:
- 代码冗余与维护困难: 随着类中字段数量的增加,尤其是引用类型字段,拷贝逻辑会变得越来越长,难以阅读和维护。每当类结构发生变化(添加或删除字段),你都必须手动更新拷贝逻辑。
- 易出错: 很容易忘记拷贝某个引用类型字段,或者错误地进行了浅拷贝(直接赋值引用而不是创建新实例),从而引入难以发现的bug。
- 循环引用问题: 如果对象图存在循环引用(A引用B,B又引用A),手动递归拷贝如果不做特殊处理,可能会导致无限递归,最终
StackOverflowError
。 - 集合与Map的处理: 对于
List
、Set
、Map
等集合类型,你需要遍历集合,对每个元素进行深拷贝,并添加到新的集合中。这比直接拷贝字段要复杂得多。
最佳实践:
- 优先使用拷贝构造器: 为你的类提供一个拷贝构造器(Copy Constructor),它接收一个同类型的对象作为参数,并在构造新对象时,递归地拷贝所有可变引用字段。这是一种封装拷贝逻辑的良好方式。
// 示例:PersonWithManualCopy(PersonWithManualCopy other)
- 提供
deepCopy()
方法: 除了拷贝构造器,你也可以在类中提供一个public Person deepCopy()
方法,返回一个当前对象的深拷贝。// 示例:public PersonWithManualCopy deepCopy()
- 对可变引用类型进行防御性拷贝: 这是核心思想。对于任何可变的引用类型字段(如
List
、Map
、自定义对象),在拷贝时都必须创建它们的新实例,并递归地拷贝其内容。对于不可变类型(如String
、Integer
等包装类),可以直接赋值,因为它们的值不会改变。 - 避免使用
Cloneable
接口和clone()
方法: Java的Cloneable
接口是一个“标记接口”,其clone()
方法默认实现的是浅拷贝。如果你要用它实现深拷贝,你需要重写clone()
方法,并在其中手动递归拷贝。但clone()
方法有很多设计缺陷(如CloneNotSupportedException
、没有调用构造器、受保护访问权限等),通常不推荐使用。拷贝构造器或自定义deepCopy()
方法是更好的选择。 - 处理循环引用(高级): 对于非常复杂的对象图,如果存在循环引用,你可能需要在拷贝过程中维护一个
Map
来记录已经拷贝过的对象,避免重复拷贝和无限递归。键是原始对象,值是其对应的拷贝对象。在拷贝一个对象前,先检查它是否已经在Map
中,如果在,直接返回其拷贝,否则进行拷贝并放入Map
。 - 考虑使用Builder模式(辅助): 对于一些复杂对象,如果其构造过程本身就很复杂,并且需要进行深拷贝,有时结合Builder模式可以简化拷贝过程,因为你可以通过Builder重新构建一个完全独立的对象。
适用场景:
- 对象不可序列化: 当你的对象(或其内部引用对象)无法实现
Serializable
接口时,手动拷贝是唯一的选择。 - 极致性能要求: 当序列化的性能开销无法接受时,手动拷贝可以避免I/O操作,直接在内存中进行复制,理论上速度更快。
- 需要精细控制拷贝过程: 当你希望对拷贝过程有完全的控制,例如只拷贝部分字段,或者在拷贝过程中进行一些转换或验证时。
- 对象结构相对简单: 对于字段数量不多、嵌套层级不深的对象,手动拷贝是可行的。
到这里,我们也就讲完了《Java深拷贝怎么实现?详解方法与技巧》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!

- 上一篇
- Golang反射调用原理:MakeFunc与Call详解

- 下一篇
- Golang策略模式详解:接口与多态实战应用
-
- 文章 · java教程 | 1小时前 |
- Spring Cloud Auth Service配置异常解决方法
- 286浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- SpringBatchKafka偏移管理与StepScope使用
- 470浏览 收藏
-
- 文章 · java教程 | 4小时前 | 流处理 Java调用Shell Runtime.exec ProcessBuilder 进程阻塞
- Java调用Shell:Runtime.exec使用全解析
- 274浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- Java操作ZIP文件详细教程
- 363浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- Java高并发线程池配置技巧
- 337浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Java多异常捕获技巧与常见问题
- 437浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Java类定义方法及语法解析
- 235浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Java中strictfp的作用及使用场景解析
- 409浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Java异常处理性能影响及优化技巧
- 325浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Java在企业开发中的实际应用
- 348浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- Semaphore如何控制并发,Java信号量原理详解
- 233浏览 收藏
-
- 文章 · java教程 | 6小时前 |
- Java实现WebSocket集群通信方案解析
- 132浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 免费AI认证证书
- 科大讯飞AI大学堂推出免费大模型工程师认证,助力您掌握AI技能,提升职场竞争力。体系化学习,实战项目,权威认证,助您成为企业级大模型应用人才。
- 33次使用
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 161次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 229次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 183次使用
-
- 稿定PPT
- 告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
- 170次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览