Java拷贝专题—— 浅度拷贝与深度拷贝,常用工具类对比及推荐

一、引言

在 Java 开发过程中,对象的拷贝操作是常见的需求场景。比如在数据传输、对象状态备份与恢复、以及处理复杂对象关系时,都需要精准地完成对象拷贝,以确保数据的一致性和正确性。然而,拷贝操作并非简单的对象赋值,它涉及到浅拷贝和深度拷贝两种不同的方式,它们在实现原理和应用场景上有着显著的差异。选择合适的拷贝方式对于程序的性能、功能实现以及代码的可维护性都至关重要。接下来,笔者将深入浅出地为大家剖析 Java 中的浅拷贝与深度拷贝,并对常用的深度拷贝工具类进行对比分析,重点聚焦于 Orika 这一强大工具在 Spring Boot 环境下的集成与使用,助力开发者们在实际项目中高效、优雅地解决对象拷贝问题。

二、浅拷贝与深度拷贝

(一)浅拷贝

  • 定义 :浅拷贝是指创建一个新对象,然后将原对象的非静态字段按值复制到新对象中。如果字段是基本数据类型,则直接复制其值;若是引用类型,则仅仅复制引用地址,新旧对象的引用字段指向相同的内存地址。即对于引用类型的成员变量,浅拷贝后的对象与原对象共享同一引用对象。
  • 实现方式 :在 Java 中,可以通过实现 Cloneable 接口并重写 clone() 方法来实现对象的浅拷贝。克隆对象通过调用父类 Object 的 clone() 方法创建。
  • 示例代码
public class ShallowCopyExample implements Cloneable {
    private int age;
    private String name;
    private Object referenceObject;

    @Override
    protected ShallowCopyExample clone() throws CloneNotSupportedException {
        return (ShallowCopyExample) super.clone();
    }

    // Getter and Setter methods
}

(二)深度拷贝

  • 定义 :深度拷贝不仅创建原对象的副本,而且对原对象中引用的其他对象也进行拷贝,递归地完成整个对象图的拷贝。深度拷贝确保新对象与其引用的其他对象与原对象及其引用对象完全独立,互不干扰。即使原对象中引用的其他对象发生变化,也不会影响到拷贝后的对象。
  • 实现方式 :实现深度拷贝有多种方式,常见的包括:
    • 手动拷贝 :开发者手动创建目标对象,并逐个字段地将原对象的值复制到目标对象中。对于引用类型字段,还需归递地进行手动拷贝。这种方式适用于对象结构简单、字段不多的情况,但对于复杂对象结构而言,手动拷贝代码繁琐且容易出错。
    • 序列化与反序列化 :将对象序列化为字节数组,再将其反序列化为新对象,从而实现深度拷贝。这要求对象及其引用的其他对象都实现 Serializable 接口。其优点是实现简单,无需关心对象内部结构,但对于性能要求较高的场景可能不太适用,因为序列化和反序列化过程会带来一定的性能开销。
    • 使用第三方工具类 :借助一些成熟的第三方库(如 Apache Commons BeanUtils、Dozer、Orika 等)来完成深度拷贝。这些工具类提供了方便、灵活的接口,能够高效地处理各类复杂对象的拷贝需求,并且在性能上通常经过优化,是开发中常用的深度拷贝实现方式。
  • 示例代码(序列化与反序列化实现深度拷贝)
import java.io.*;

public class DeepCopyExample implements Serializable {
    private int age;
    private String name;
    private Object referenceObject;

    public DeepCopyExample deepCopy() throws IOException, ClassNotFoundException {
        // 序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

        // 反序列化
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return ( DeepCopyExample) objectInputStream.readObject();
    }

    // Getter and Setter methods
}

三、常用深度拷贝工具类对比

(一)Apache Commons BeanUtils

  • 简介 :Apache Commons BeanUtils 是一款功能强大的 Java 工具库,提供了对 Java Bean 的操作功能,包括属性的读取、设置、拷贝等。在深度拷贝方面,它可以用于简单对象的属性拷贝,但对复杂对象(如包含嵌套对象、集合等)的处理可能较为复杂。
  • 优点
    • 对于简单对象的属性拷贝,代码简洁,易于使用。
    • 是 Apache 社区出品,经过广泛的使用和验证,具有一定的稳定性和可靠性。
  • 缺点
    • 对复杂对象的深度拷贝支持不够完善,需要开发者手动处理嵌套对象的拷贝逻辑。
    • 在性能方面,相较于专门的深度拷贝工具类,稍逊一筹。

(二)Dozer

  • 简介 :Dozer 是一个开源的对象 - 对象映射工具,专注于解决 Java 对象之间的映射和转换问题。它能够实现对象的深度拷贝,并支持复杂的映射规则配置。
  • 优点
    • 支持复杂的对象映射和转换,包括嵌套、对象集合、自定义映射规则等。
    • 提供了丰富的配置选项,可以通过 XML 配置文件或注解的方式定义映射关系,灵活性高。
  • 缺点
    • 在处理大规模对象转换时,性能可能存在瓶颈。
    • 配置相对复杂,对于简单的对象拷贝场景,可能会显得繁琐。

(三)Orika

  • 简介 :Orika 是一款高性能的对象映射框架,专注于快速、高效地完成 Java 对象之间的映射和转换,包括深度拷贝。它通过代码生成和优化技术,在运行时生成高效的映射代码,从而提供卓越的性能表现。
  • 优点
    • 性能卓越,尤其在处理大量对象映射时,相比其他工具类具有明显的优势。
    • 易于使用,提供了简洁的 API,能够快速完成对象映射和深度拷贝操作。
    • 支持复杂对象的映射,包括嵌套对象、集合、自定义类型转换等,并能够自动处理常见的映射场景,减少开发者的配置工作量。
  • 缺点
    • 需要在项目中引入额外的依赖,并且需要一定的配置初始化工作。
    • 对于非常简单的对象拷贝场景,可能略显复杂。

(四)对比总结

在选择深度拷贝工具类时,应综合考虑项目的具体需求。如果项目中对象结构较为简单,且对性能要求不高,Apache Commons BeanUtils 可能是一个轻量级的选择;对于需要处理复杂对象映射和转换的场景,Dozer 提供了强大的功能和灵活性;而当项目对性能要求极高,并且涉及大量对象的深度拷贝操作时,Orika 凭借其出色的性能和易用性,成为了最佳选择之一。在实际开发中,Orika 凭借其在性能和易用性方面的优势,逐渐受到开发者的青睐,特别是在基于 Spring Boot 的微服务架构项目中,Orika 与 Spring Boot 的集成非常方便,能够快速融入项目开发流程,提升开发效率。

四、Orika 介绍

Orika 是一款基于 Java 的对象 - 对象映射框架,专注于为开发者提供高性能、灵活且易于使用的对象映射解决方案。它采用了代码生成(code generation)技术,在运行时动态生成映射代码,从而极大地提高了映射操作的效率和性能。与传统的映射方式(如逐个字段手动映射或通过反射进行映射)相比,Orika 的性能优势尤为显著,尤其适用于需要频繁进行大规模对象映射的场景,如微服务架构中的数据传输对象(DTO)与实体对象(Entity)、视图模型(ViewModel)之间的转换等。Orika 的主要特点包括:

  • 高效性能 :通过代码生成技术优化映射过程,减少反射操作,提升映射效率。
  • 简单易用 :提供简洁直观的 API,使得对象映射配置和操作变得轻松快捷。
  • 灵活映射 :支持复杂对象映射,包括嵌套对象、集合、自定义类型转换等。允许开发者定义映射规则、忽略特定字段、添加自定义转换器等,以满足各种复杂的业务需求。
  • 自动映射 :能够自动处理常见的映射场景,如字段名相同或遵循一定命名规则的字段映射,减少开发者的配置工作量。

五、Spring Boot 下集成与使用 OriKa

(一)添加依赖

在项目中使用 OriKa,首先需要在项目的 build.gradle(对于 Gradle 项目)或 pom.xml(对于 Maven 项目)中添加 OriKa 的相关依赖。以下是 Maven 项目中添加依赖的示例代码:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

(二)配置 OriKa

创建一个 OriKa 的配置类,用于初始化 OriKa 映射器(Mapper)。示例代码如下:

import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.ConfigurableMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OriKaConfig {

    @Bean
    public ConfigurableMapper configurableMapper() {
        return new ConfigurableMapper() {
            @Override
            public void configure(MapperFactory factory) {
                // 可以在这里配置全局的映射规则、自定义转换器等
            }
        };
    }
}

(三)定义映射关系

通过定义映射关系类,指定源对象和目标对象之间的映射规则。可以使用注解的方式或者通过配置类来进行映射关系的定义。以下是通过配置类定义映射关系的示例代码:

import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.ConfigurableMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OriKaConfig {

    @Bean
    public ConfigurableMapper configurableMapper() {
        return new ConfigurableMapper() {
            @Override
            public void configure(MapperFactory factory) {
                // 定义映射规则
                factory.classMap(SourceClass.class, TargetClass.class)
                    .field("sourceField1", "targetField1")
                    .field("sourceField2", "targetField2")
                    .byDefault()
                    .register();
            }
        };
    }
}

注:该步骤并不是必须的,只是可采用该方式对某一个具体类及其属性进行定制化配置。

(四)使用 OriKa 进行深度拷贝

在业务代码中,通过注入 MapperFacade 或 MapperFactory Bean,即可使用 OriKa 进行对象的深度拷贝操作。

例如,我们在控制器类中,使用MapperFacade,将实体类转换为视图对象vo,示例代码如下:

/**
 * 用户 前端控制器类
 *
 * @author wqliu
 * @date 2023-04-23
 */
@RestController
@RequestMapping("/system/user")
@Slf4j
public class UserController extends BaseController {

    @Autowired
    private MapperFacade mapperFacade;

    private UserVO convert2VO(User entity) {
        UserVO vo = mapperFacade.map(entity, UserVO.class);      
        return vo;
    }
}

六、总结

在 Java 开发领域,对象的拷贝操作是不可或缺的一环。深刻理解浅拷贝和深度拷贝的概念、原理及区别,对于正确处理对象的状态和行为具有重要意义。不同的深度拷贝工具类各具特点,适用于不同的开发场景。Apache Commons BeanUtils 适合简单对象的快速拷贝,Dozer 在复杂对象映射方面表现出色,而 OriKa 凭借其卓越的性能和便捷的使用方式,在处理大规模、高性能要求的深度拷贝场景时成为了首选。在基于 Spring Boot 的项目中,Orika 的集成与使用进一步简化了开发流程,提高了开发效率。掌握这些工具类的使用技巧,能够帮助开发者更加高效、准确地完成对象的深度拷贝任务,提升项目的整体质量。在实际项目开发中,根据业务需求和项目特点合理选择深度拷贝工具类,并深入学习其高级特性和优化技巧,将有助于打造更加健壮、高效、可维护的 Java 应用系统。

如果您在阅读本文时获得了帮助或受到了启发,希望您能够喜欢并收藏这篇文章,为它点赞~

请在评论区与我分享您的想法和心得,一起交流学习,不断进步,遇见更加优秀的自己!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行者无疆1982

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值