如何高效进行对象拷贝?浅拷贝与深拷贝的陷阱,你知道吗?

发布时间:2026/10/14 1:57:00
如何高效进行对象拷贝?浅拷贝与深拷贝的陷阱,你知道吗? 全文目录开篇语前言一、浅拷贝与深拷贝它们的区别与陷阱1. 什么是浅拷贝2. 什么是深拷贝二、对象属性复制的正确方式Spring BeanUtils、Apache BeanUtils 与 MapStruct1. 使用 Spring 的 BeanUtils2. 使用 Apache 的 BeanUtils3. 使用 MapStruct4. 性能比较BeanUtils vs 手写 vs MapStruct三、实战场景DTO 与 Entity 之间的转换四、总结文末开篇语哈喽各位小伙伴们你们好呀我是喵手。运营社区C站/掘金/腾讯云/阿里云/华为云/51CTO欢迎大家常来逛逛今天我要给大家分享一些自己日常学习到的一些知识点并以文字的形式跟大家一起交流互相学习一个人虽可以走的更快但一群人可以走的更远。我是一名后端开发爱好者工作日常接触到最多的就是Java语言啦所以我都尽量抽业余时间把自己所学到所会的通过文章的形式进行输出希望以这种方式帮助到更多的初学者或者想入门的小伙伴们同时也能对自己的技术进行沉淀加以复盘查缺补漏。小伙伴们在批阅的过程中如果觉得文章不错欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持前言我们在日常开发中常常需要在对象之间进行数据传递尤其是在不同层之间的对象转换。例如在数据层使用实体类Entity在服务层或传输层使用数据传输对象DTO。每次我们将一个对象的属性复制到另一个对象时都面临着一个重要的决定应该使用浅拷贝还是深拷贝不同的拷贝方式会直接影响我们的程序运行和数据的独立性。对于许多开发者来说浅拷贝和深拷贝之间的差异可能并不十分明显但它们之间的微妙区别却决定了数据是否会“共享”或“隔离”。本篇文章我们将通过对浅拷贝与深拷贝的讨论以及 Spring 和 Apache 提供的BeanUtils和更现代化的MapStruct的使用深入探讨如何在实际开发中高效地进行对象属性复制。我们还将对比不同方法的性能帮助你在开发中做出合适的选择。一、浅拷贝与深拷贝它们的区别与陷阱1. 什么是浅拷贝浅拷贝的核心思想是如果对象中包含基本类型的属性它们会被直接复制如果对象中包含引用类型的属性那么这些引用类型的属性只会复制引用即内存地址而不会复制引用对象本身。这就意味着原始对象和拷贝对象中引用类型的属性会指向同一块内存空间。我们通过一个例子来看看浅拷贝的实际情况publicclassPerson{privateStringname;privateAddressaddress;// Getters and Setters}publicclassAddress{privateStringcity;privateStringstreet;// Getters and Setters}/** * Author ms * Date 2025年6月22日20:43:49 */publicclassTest1{publicstaticvoidmain(String[]args){// 浅拷贝示例Personperson1newPerson(John,newAddress(New York,5th Avenue));Personperson2person1;// 只是引用复制指向相同的内存person2.getAddress().setCity(San Francisco);System.out.println(person1.getAddress().getCity());// 输出 San Francisco}}在这个例子中person1和person2是指向同一个Address对象的引用。因此当我们修改person2中的address时person1的address也会被修改。这就是浅拷贝的特性引用类型字段共享同一块内存区域。本地测试结果展示如下根据如上测试用例我本地演示结果展示如下仅供参考哈你们也可以自行修改测试用例或者添加更多的测试数据或测试方法进行熟练学习以此加深理解。代码解析针对如上示例代码这里我给大家详细的代码剖析下以便于帮助大家理解的更为透彻帮助大家早日掌握。如下这段代码案例我演示了 Java 中“浅拷贝”的概念以下是详细解析 程序核心思想浅拷贝这段代码案例展示了一个对象被赋值给另一个变量后两者引用同一个内存地址的现象因此修改一个对象的内部属性也会影响另一个对象。 示例对象结构程序中使用了两个类虽然未明示但根据代码逻辑可推断Person拥有name和Address成员。Address拥有city和street等属性。 具体过程解释创建了一个Person对象person1内部包含一个Address对象城市是 New York。然后将person1的引用赋值给person2这并不是创建了一个新对象而是person2和person1指向了同一个 Person 对象。修改person2的地址对象将城市名改为 “San Francisco”。最后打印person1的地址城市结果是 “San Francisco”。 原因分析这是浅拷贝shallow copyperson2 person1并没有复制person1的数据而是复制了引用所以person1.getAddress()和person2.getAddress()返回的是同一个 Address 实例修改其中一个的地址就影响了另一个。✅ 总结这段代码的目的是说明Java 中对象赋值是引用复制而不是值复制。要真正创建一个不受原对象影响的副本需要使用深拷贝deep copy策略比如手动复制每一层对象使用序列化或第三方工具类如 Apache Commons Lang 的SerializationUtils.clone()。这个浅拷贝示例在开发中非常常见理解它对避免意外共享对象副作用非常关键。2. 什么是深拷贝与浅拷贝不同深拷贝会创建一个新对象并递归地复制原始对象及其所有引用类型的字段确保新对象和原对象之间完全独立。换句话说深拷贝不仅复制对象本身还会复制对象所引用的所有其他对象。因此修改新对象中的字段不会影响原对象。我们通过一个深拷贝的例子来说明publicclassPersonimplementsCloneable{privateStringname;privateAddressaddress;// Getter and SetterOverridepublicObjectclone()throwsCloneNotSupportedException{Personperson(Person)super.clone();person.setAddress((Address)person.getAddress().clone());returnperson;}}// 深拷贝示例Personperson1newPerson(John,newAddress(New York,5th Avenue));Personperson2(Person)person1.clone();// 深拷贝两个对象完全独立person2.getAddress().setCity(San Francisco);System.out.println(person1.getAddress().getCity());// 输出 New York在这个例子中person1和person2是完全独立的对象修改person2的address字段不会影响person1。这是深拷贝的特性确保了对象的完全复制。二、对象属性复制的正确方式Spring BeanUtils、Apache BeanUtils 与 MapStruct在 Java 中如果我们想要复制一个对象的属性到另一个对象可以通过手写代码、使用 Spring 或 Apache 提供的BeanUtils或者采用更现代化的MapStruct来实现。接下来我们会对这些方法进行详细分析看看它们的优缺点以及适用场景。1. 使用 Spring 的 BeanUtilsSpring 的BeanUtils是最常用的属性复制工具之一。BeanUtils.copyProperties()方法能够将源对象的属性值复制到目标对象中适用于简单的对象属性复制场景。代码实现如下importorg.springframework.beans.BeanUtils;publicclassExample{publicstaticvoidmain(String[]args){SourceBeansourcenewSourceBean(John,25);TargetBeantargetnewTargetBean();BeanUtils.copyProperties(source,target);System.out.println(target.getName());// 输出 JohnSystem.out.println(target.getAge());// 输出 25}}classSourceBean{privateStringname;privateintage;// Getters and Setters}classTargetBean{privateStringname;privateintage;// Getters and Setters}优点简单易用不需要写复杂的代码。可以处理基础类型和属性的复制。缺点性能较差BeanUtils基于反射实现属性复制效率低尤其是对于大对象或频繁调用时。不支持深拷贝它仅支持浅拷贝对于包含引用类型字段的对象它不会进行深拷贝。2. 使用 Apache 的 BeanUtilsApache 的BeanUtils和 Spring 提供的BeanUtils类似也是基于反射的属性复制工具。使用方法和 Spring 基本相同代码示例如下importorg.apache.commons.beanutils.BeanUtils;publicclassExample{publicstaticvoidmain(String[]args)throwsException{SourceBeansourcenewSourceBean(John,25);TargetBeantargetnewTargetBean();BeanUtils.copyProperties(target,source);System.out.println(target.getName());// 输出 JohnSystem.out.println(target.getAge());// 输出 25}}classSourceBean{privateStringname;privateintage;// Getters and Setters}classTargetBean{privateStringname;privateintage;// Getters and Setters}优缺点与 Spring 的BeanUtils类似主要区别在于它是 Apache Commons 提供的工具类且性能问题同样存在。3. 使用 MapStructMapStruct 是近年来流行的一种代码生成工具它通过注解生成高效的属性复制代码避免了反射的性能开销。与 Spring 或 Apache 的BeanUtils不同MapStruct 在编译时生成映射代码使用时无需反射因此性能非常高。importorg.mapstruct.Mapper;importorg.mapstruct.factory.Mappers;MapperpublicinterfacePersonMapper{PersonMapperINSTANCEMappers.getMapper(PersonMapper.class);TargetBeansourceToTarget(SourceBeansource);}classSourceBean{privateStringname;privateintage;// Getters and Setters}classTargetBean{privateStringname;privateintage;// Getters and Setters}MapStruct 会在编译时生成具体的转换代码而不是在运行时反射这使得它的性能要比BeanUtils高得多尤其是在复杂对象的转换上。优点高性能通过编译时生成代码避免了反射带来的性能开销。支持深拷贝MapStruct 能够处理更复杂的类型转换包括深拷贝和多层嵌套对象的转换。缺点学习成本较高需要了解注解和接口的使用以及如何配置 MapStruct。配置稍微复杂需要依赖构建工具来生成代码如 Maven 或 Gradle。4. 性能比较BeanUtils vs 手写 vs MapStruct在性能上MapStruct的优势非常明显它通过在编译时生成代码避免了反射的性能瓶颈。BeanUtils和Apache BeanUtils都使用了反射机制性能较差尤其是在需要频繁进行属性复制的场景中可能会成为瓶颈。我们可以通过以下流程图直观地了解这三者之间的差异性能最优性能较好性能较差Apache BeanUtils三、实战场景DTO 与 Entity 之间的转换在企业级开发中DTO 和 Entity 之间的转换是常见的需求。DTO数据传输对象通常用于前端数据交互而 Entity实体类则代表数据库中的数据模型。我们需要将数据库查询得到的实体类转换成 DTO以便传输给前端或进行进一步处理。使用BeanUtils进行 DTO 与 Entity 之间的转换非常简单但在复杂的数据结构转换场景中推荐使用MapStruct它能高效、准确地完成深拷贝确保对象之间完全独立。MapperpublicinterfaceUserMapper{UserMapperINSTANCEMappers.getMapper(UserMapper.class);UserDTOentityToDto(UserEntityentity);UserEntitydtoToEntity(UserDTOdto);}classUserEntity{privateStringname;privateStringemail;// Getters and Setters}classUserDTO{privateStringname;privateStringemail;// Getters and Setters}通过 MapStruct我们可以快速生成 DTO 和 Entity 之间的转换代码避免手动编写大量冗余的转换代码。代码解析针对如上示例代码这里我给大家详细的代码剖析下以便于帮助大家理解的更为透彻帮助大家早日掌握。如上这段代码展示了如何使用MapStruct框架在 Java 中进行对象映射Object Mapping也就是将一个类的对象转换为另一个类的对象。以下是详细代码解析背景什么是对象映射在实际开发中常常会有不同层的对象模型例如Entity用于数据库持久化JPA/HibernateDTOData Transfer Object用于数据传输特别是在前后端交互或服务调用中。虽然这些对象字段可能相同或相似但通常不能直接互用因此需要手动写转换逻辑这会导致大量重复代码。 MapStruct 的作用MapStruct是一个编译期注解处理器它可以自动为我们生成这些对象转换的代码快速类型安全不用手写 getter/setter 映射逻辑。代码详解接口定义UserMapperMapperpublicinterfaceUserMapper{UserMapperINSTANCEMappers.getMapper(UserMapper.class);Mapper标记这是一个 MapStruct 映射接口INSTANCE这是获取自动生成的 Mapper 实例的标准做法Mappers.getMapper(...)MapStruct 在编译期会生成实现类。映射方法UserDTOentityToDto(UserEntityentity);UserEntitydtoToEntity(UserDTOdto);这两个方法定义了从Entity到DTO以及反向的转换MapStruct 会根据字段名称和类型相同的原则自动完成映射逻辑。 类定义UserEntity和UserDTO这两个类都有两个相同的字段name和email并包含 getter 和 setter。由于字段名一致MapStruct 可以自动完成对应字段的映射无需额外配置。✅ 总结这段代码通过使用 MapStruct避免了手动编写繁琐的字段赋值保持了 DTO 和 Entity 的独立性让数据层与表示层之间的转换变得高效、规范是企业级应用开发中常用的模型转换方案。要使这段代码实际运行还需添加相关依赖如在 Maven 中引入mapstruct和mapstruct-processor并在构建时启用注解处理。四、总结通过本文的讲解我们深入了解了对象拷贝中浅拷贝和深拷贝的区别以及它们可能带来的陷阱。同时探讨了如何利用工具类如 Spring 的BeanUtils、Apache 的BeanUtils和MapStruct高效地进行对象属性复制。对于大多数简单场景BeanUtils已经足够满足需求但在处理更复杂的对象转换时尤其是需要深拷贝的情况MapStruct绝对是一个更高效的选择。在实际开发中选择合适的工具将大大提升开发效率和代码质量。希望通过本文的讨论你能更加清楚地了解对象拷贝的正确方式避免陷入浅拷贝与深拷贝的陷阱编写出高效、可维护的代码。… …文末好啦以上就是我这期的全部内容如果有任何疑问欢迎下方留言哦咱们下期见。… …学习不分先后知识不分多少事无巨细当以虚心求教三人行必有我师焉wished for you successed ⭐️若喜欢我就请关注我叭。⭐️若对您有用就请点赞叭。⭐️若有疑问就请评论留言告诉我叭。版权声明本文由作者原创转载请注明出处谢谢支持

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询